diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 2d1db716b700..ebf5832147f7 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -79,5 +79,6 @@ + diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index dfe683dbee9a..eb7fea3eb89a 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -319,6 +319,9 @@ + + + diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index fc85a85d0acb..6d727b4cf966 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -61,6 +61,7 @@ import android.os.ServiceManager; import android.os.UserManager; import android.os.Vibrator; import android.permission.PermissionManager; +import android.safetycenter.SafetyCenterManager; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.telecom.TelecomManager; @@ -476,4 +477,11 @@ public class FrameworkServicesModule { static SmartspaceManager provideSmartspaceManager(Context context) { return context.getSystemService(SmartspaceManager.class); } + + @Provides + @Singleton + static SafetyCenterManager provideSafetyCenterManager(Context context) { + return context.getSystemService(SafetyCenterManager.class); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt index 95b4b725e4c6..dc79f40ffef6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt @@ -1,15 +1,19 @@ package com.android.systemui.qs +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.permission.PermissionGroupUsage import android.permission.PermissionManager -import android.provider.DeviceConfig +import android.safetycenter.SafetyCenterManager import android.view.View import androidx.annotation.WorkerThread import com.android.internal.R import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.appops.AppOpsController +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.privacy.PrivacyChipEvent @@ -18,7 +22,6 @@ import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.statusbar.phone.StatusIconContainer -import com.android.systemui.util.DeviceConfigProxy import java.util.concurrent.Executor import javax.inject.Inject import com.android.systemui.dagger.qualifiers.Background @@ -50,15 +53,11 @@ class HeaderPrivacyIconsController @Inject constructor( @Main private val uiExecutor: Executor, private val activityStarter: ActivityStarter, private val appOpsController: AppOpsController, - private val deviceConfigProxy: DeviceConfigProxy + private val broadcastDispatcher: BroadcastDispatcher, + private val safetyCenterManager: SafetyCenterManager ) { - companion object { - const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled" - } - var chipVisibilityListener: ChipVisibilityListener? = null - private var listening = false private var micCameraIndicatorsEnabled = false private var locationIndicatorsEnabled = false @@ -68,20 +67,40 @@ class HeaderPrivacyIconsController @Inject constructor( private val micSlot = privacyChip.resources.getString(R.string.status_bar_microphone) private val locationSlot = privacyChip.resources.getString(R.string.status_bar_location) - private val devicePropertiesChangedListener = - object : DeviceConfig.OnPropertiesChangedListener { - override fun onPropertiesChanged(properties: DeviceConfig.Properties) { - safetyCenterEnabled = properties.getBoolean(SAFETY_CENTER_ENABLED, false) - } + private val safetyCenterReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + safetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled() + } + } + + val attachStateChangeListener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + broadcastDispatcher.registerReceiver( + safetyCenterReceiver, + IntentFilter(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED), + executor = backgroundExecutor + ) } + override fun onViewDetachedFromWindow(v: View) { + broadcastDispatcher.unregisterReceiver(safetyCenterReceiver) + } + } + init { - safetyCenterEnabled = deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - SAFETY_CENTER_ENABLED, false) - deviceConfigProxy.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_PRIVACY, - uiExecutor, - devicePropertiesChangedListener) + backgroundExecutor.execute { + safetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled() + } + + if (privacyChip.isAttachedToWindow()) { + broadcastDispatcher.registerReceiver( + safetyCenterReceiver, + IntentFilter(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED), + executor = backgroundExecutor + ) + } + + privacyChip.addOnAttachStateChangeListener(attachStateChangeListener) } private val picCallback: PrivacyItemController.Callback = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt index 30a5308d9334..a67483b10c4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt @@ -1,14 +1,18 @@ package com.android.systemui.qs +import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.permission.PermissionManager -import android.provider.DeviceConfig +import android.safetycenter.SafetyCenterManager import android.testing.AndroidTestingRunner import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpsController +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.privacy.PrivacyDialogController @@ -16,30 +20,30 @@ import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.DeviceConfigProxy -import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor import org.mockito.Mockito.`when` as whenever +private fun eq(value: T): T = Mockito.eq(value) ?: value +private fun any(): T = Mockito.any() + @SmallTest @RunWith(AndroidTestingRunner::class) class HeaderPrivacyIconsControllerTest : SysuiTestCase() { - companion object { - const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled" - } - @Mock private lateinit var privacyItemController: PrivacyItemController @Mock @@ -55,14 +59,16 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { @Mock private lateinit var permissionManager: PermissionManager @Mock - private lateinit var backgroundExecutor: Executor - @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var appOpsController: AppOpsController + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock + private lateinit var safetyCenterManager: SafetyCenterManager private val uiExecutor = FakeExecutor(FakeSystemClock()) - private lateinit var deviceConfigProxy: DeviceConfigProxy + private val backgroundExecutor = FakeExecutor(FakeSystemClock()) private lateinit var cameraSlotName: String private lateinit var microphoneSlotName: String private lateinit var locationSlotName: String @@ -73,11 +79,11 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(privacyChip.context).thenReturn(context) whenever(privacyChip.resources).thenReturn(context.resources) + whenever(privacyChip.isAttachedToWindow).thenReturn(true) cameraSlotName = context.getString(com.android.internal.R.string.status_bar_camera) microphoneSlotName = context.getString(com.android.internal.R.string.status_bar_microphone) locationSlotName = context.getString(com.android.internal.R.string.status_bar_location) - deviceConfigProxy = DeviceConfigProxyFake() controller = HeaderPrivacyIconsController( privacyItemController, @@ -91,8 +97,11 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { uiExecutor, activityStarter, appOpsController, - deviceConfigProxy + broadcastDispatcher, + safetyCenterManager ) + + backgroundExecutor.runAllReady() } @Test @@ -141,19 +150,25 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { @Test fun testPrivacyChipClicked() { - changeProperty(SAFETY_CENTER_ENABLED, false) + whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false) controller.onParentVisible() - val captor = argumentCaptor() verify(privacyChip).setOnClickListener(capture(captor)) captor.value.onClick(privacyChip) - verify(privacyDialogController).showDialog(any(Context::class.java)) } @Test fun testSafetyCenterFlag() { - changeProperty(SAFETY_CENTER_ENABLED, true) + val receiverCaptor = argumentCaptor() + whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true) + verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor), + any(), any(), nullable(), anyInt()) + receiverCaptor.value.onReceive( + context, + Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED) + ) + backgroundExecutor.runAllReady() controller.onParentVisible() val captor = argumentCaptor() verify(privacyChip).setOnClickListener(capture(captor)) @@ -161,18 +176,29 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { verify(privacyDialogController, never()).showDialog(any(Context::class.java)) } + @Test + fun testBroadcastReceiverUnregisteredWhenChipDetached() { + whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false) + controller.attachStateChangeListener.onViewDetachedFromWindow(privacyChip) + backgroundExecutor.runAllReady() + val broadcastReceiverCaptor = argumentCaptor() + verify(broadcastDispatcher).unregisterReceiver(capture(broadcastReceiverCaptor)) + } + + @Test + fun testBroadcastReceiverRegisteredWhenChipAttached() { + whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false) + controller.attachStateChangeListener.onViewAttachedToWindow(privacyChip) + backgroundExecutor.runAllReady() + val broadcastReceiverCaptor = argumentCaptor() + val intentFilterCaptor = argumentCaptor() + // Broadcast receiver is registered on init and when privacy chip is attached + verify(broadcastDispatcher, times(2)).registerReceiver(capture(broadcastReceiverCaptor), + capture(intentFilterCaptor), any(), nullable(), anyInt()) + } + private fun setPrivacyController(micCamera: Boolean, location: Boolean) { whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera) whenever(privacyItemController.locationAvailable).thenReturn(location) } - - private fun changeProperty(name: String, value: Boolean?) { - deviceConfigProxy.setProperty( - DeviceConfig.NAMESPACE_PRIVACY, - name, - value?.toString(), - false - ) - uiExecutor.runAllReady() - } } \ No newline at end of file