Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +63 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,9 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.UserInfo import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager import android.safetycenter.SafetyCenterManager import android.view.View Loading @@ -21,6 +24,7 @@ import com.android.systemui.privacy.PrivacyDialogController import com.android.systemui.privacy.PrivacyDialogControllerV2 import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.DeviceProvisionedController Loading @@ -32,6 +36,8 @@ import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -81,6 +87,9 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var userTracker: UserTracker private val uiExecutor = FakeExecutor(FakeSystemClock()) private val backgroundExecutor = FakeExecutor(FakeSystemClock()) private lateinit var cameraSlotName: String Loading Loading @@ -118,6 +127,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { deviceProvisionedController, featureFlags, kosmos.shadeDialogContextInteractor, userTracker, ) backgroundExecutor.runAllReady() Loading Loading @@ -269,8 +279,61 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { verify(privacyDialogController, never()).showDialog(any(Context::class.java)) } @Test fun testPermGroupUsage_filtersOutInactiveUsers() { whenever(userTracker.userProfiles).thenReturn(listOf(createUserInfo(USER_ID))) whenever(permissionManager.getIndicatorAppOpUsageData(false)) .thenReturn(listOf(createPermUsage(USER_ID), createPermUsage(OTHER_USER_ID))) val usages = controller.permGroupUsage() assertEquals(1, usages.size) assertEquals(USER_ID, UserHandle.getUserId(usages[0].uid)) } @Test fun testPermGroupUsage_alwaysReturnPhoneCallUsage() { whenever(userTracker.userProfiles).thenReturn(listOf(createUserInfo(USER_ID))) whenever(permissionManager.getIndicatorAppOpUsageData(false)) .thenReturn(listOf(createPermUsage(OTHER_USER_ID, isPhone = true))) val usages = controller.permGroupUsage() assertEquals(OTHER_USER_ID, UserHandle.getUserId(usages[0].uid)) } @Test fun testPermGroupUsage_returnsProfileUsages() { whenever(userTracker.userProfiles) .thenReturn(listOf(createUserInfo(USER_ID), createUserInfo(PROFILE_USER_ID))) whenever(permissionManager.getIndicatorAppOpUsageData(false)) .thenReturn(listOf(createPermUsage(USER_ID), createPermUsage(PROFILE_USER_ID))) val usages = controller.permGroupUsage() assertEquals(2, usages.size) assertNotNull(usages.firstOrNull { UserHandle.getUserId(it.uid) == USER_ID }) assertNotNull(usages.firstOrNull { UserHandle.getUserId(it.uid) == PROFILE_USER_ID }) } private fun createPermUsage(user: Int, isPhone: Boolean = false): PermissionGroupUsage = PermissionGroupUsage( "", UserHandle.getUid(user, 0), 0, "", true, isPhone, null, null, null, "", ) private fun createUserInfo(userId: Int) = UserInfo(userId, "", 0) private fun setPrivacyController(micCamera: Boolean, location: Boolean) { whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera) whenever(privacyItemController.locationAvailable).thenReturn(location) } companion object { const val USER_ID = 0 const val PROFILE_USER_ID = 1 const val OTHER_USER_ID = 2 } } packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt +75 −51 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager import android.safetycenter.SafetyCenterManager Loading @@ -14,6 +15,8 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.appops.AppOpsController import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter Loading @@ -24,14 +27,13 @@ import com.android.systemui.privacy.PrivacyDialogControllerV2 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 java.util.concurrent.Executor import javax.inject.Inject import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.DeviceProvisionedController import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Named interface ChipVisibilityListener { Loading @@ -40,15 +42,17 @@ interface ChipVisibilityListener { /** * Controls privacy icons/chip residing in QS header which show up when app is using camera, * microphone or location. * Manages their visibility depending on privacy signals coming from [PrivacyItemController]. * microphone or location. Manages their visibility depending on privacy signals coming from * [PrivacyItemController]. * * Unlike typical controller extending [com.android.systemui.util.ViewController] this view doesn't * observe its attachment state because depending on where it is used, it might be never detached. * Instead, parent controller should use [onParentVisible] and [onParentInvisible] to "activate" or * "deactivate" this controller. */ class HeaderPrivacyIconsController @Inject constructor( class HeaderPrivacyIconsController @Inject constructor( private val privacyItemController: PrivacyItemController, private val uiEventLogger: UiEventLogger, @Named(SHADE_HEADER) private val privacyChip: OngoingPrivacyChip, Loading @@ -66,6 +70,7 @@ class HeaderPrivacyIconsController @Inject constructor( private val deviceProvisionedController: DeviceProvisionedController, private val featureFlags: FeatureFlags, private val shadeDialogContextInteractor: ShadeDialogContextInteractor, private val userTracker: UserTracker, ) { var chipVisibilityListener: ChipVisibilityListener? = null Loading @@ -80,18 +85,20 @@ class HeaderPrivacyIconsController @Inject constructor( private val dialogContext: Context get() = shadeDialogContextInteractor.context private val safetyCenterReceiver = object : BroadcastReceiver() { private val safetyCenterReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { safetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled() } } val attachStateChangeListener = object : View.OnAttachStateChangeListener { val attachStateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { broadcastDispatcher.registerReceiver( safetyCenterReceiver, IntentFilter(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED), executor = backgroundExecutor executor = backgroundExecutor, ) } Loading @@ -109,7 +116,7 @@ class HeaderPrivacyIconsController @Inject constructor( broadcastDispatcher.registerReceiver( safetyCenterReceiver, IntentFilter(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED), executor = backgroundExecutor executor = backgroundExecutor, ) } Loading Loading @@ -171,22 +178,39 @@ class HeaderPrivacyIconsController @Inject constructor( private fun showSafetyCenter() { backgroundExecutor.execute { val usage = ArrayList(permGroupUsage()) val usage = permGroupUsage() privacyLogger.logUnfilteredPermGroupUsage(usage) val startSafetyCenter = Intent(Intent.ACTION_VIEW_SAFETY_CENTER_QS) startSafetyCenter.putParcelableArrayListExtra(PermissionManager.EXTRA_PERMISSION_USAGES, usage) startSafetyCenter.putParcelableArrayListExtra( PermissionManager.EXTRA_PERMISSION_USAGES, usage, ) startSafetyCenter.flags = Intent.FLAG_ACTIVITY_NEW_TASK uiExecutor.execute { activityStarter.startActivity(startSafetyCenter, true, ActivityTransitionAnimator.Controller.fromView(privacyChip)) activityStarter.startActivity( startSafetyCenter, true, ActivityTransitionAnimator.Controller.fromView(privacyChip), ) } } } @WorkerThread private fun permGroupUsage(): List<PermissionGroupUsage> { return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted) fun permGroupUsage(): ArrayList<PermissionGroupUsage> { val usages = ArrayList(permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)) val invalidUserUsages = mutableListOf<PermissionGroupUsage>() val userProfiles = userTracker.userProfiles for (usage in usages) { val userId = UserHandle.getUserId(usage.uid) if (usage.isPhoneCall || userProfiles.any { it.id == userId }) { continue } invalidUserUsages.add(usage) } usages.removeAll(invalidUserUsages) return usages } fun onParentInvisible() { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +63 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,9 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.UserInfo import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager import android.safetycenter.SafetyCenterManager import android.view.View Loading @@ -21,6 +24,7 @@ import com.android.systemui.privacy.PrivacyDialogController import com.android.systemui.privacy.PrivacyDialogControllerV2 import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.DeviceProvisionedController Loading @@ -32,6 +36,8 @@ import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith Loading Loading @@ -81,6 +87,9 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var userTracker: UserTracker private val uiExecutor = FakeExecutor(FakeSystemClock()) private val backgroundExecutor = FakeExecutor(FakeSystemClock()) private lateinit var cameraSlotName: String Loading Loading @@ -118,6 +127,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { deviceProvisionedController, featureFlags, kosmos.shadeDialogContextInteractor, userTracker, ) backgroundExecutor.runAllReady() Loading Loading @@ -269,8 +279,61 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { verify(privacyDialogController, never()).showDialog(any(Context::class.java)) } @Test fun testPermGroupUsage_filtersOutInactiveUsers() { whenever(userTracker.userProfiles).thenReturn(listOf(createUserInfo(USER_ID))) whenever(permissionManager.getIndicatorAppOpUsageData(false)) .thenReturn(listOf(createPermUsage(USER_ID), createPermUsage(OTHER_USER_ID))) val usages = controller.permGroupUsage() assertEquals(1, usages.size) assertEquals(USER_ID, UserHandle.getUserId(usages[0].uid)) } @Test fun testPermGroupUsage_alwaysReturnPhoneCallUsage() { whenever(userTracker.userProfiles).thenReturn(listOf(createUserInfo(USER_ID))) whenever(permissionManager.getIndicatorAppOpUsageData(false)) .thenReturn(listOf(createPermUsage(OTHER_USER_ID, isPhone = true))) val usages = controller.permGroupUsage() assertEquals(OTHER_USER_ID, UserHandle.getUserId(usages[0].uid)) } @Test fun testPermGroupUsage_returnsProfileUsages() { whenever(userTracker.userProfiles) .thenReturn(listOf(createUserInfo(USER_ID), createUserInfo(PROFILE_USER_ID))) whenever(permissionManager.getIndicatorAppOpUsageData(false)) .thenReturn(listOf(createPermUsage(USER_ID), createPermUsage(PROFILE_USER_ID))) val usages = controller.permGroupUsage() assertEquals(2, usages.size) assertNotNull(usages.firstOrNull { UserHandle.getUserId(it.uid) == USER_ID }) assertNotNull(usages.firstOrNull { UserHandle.getUserId(it.uid) == PROFILE_USER_ID }) } private fun createPermUsage(user: Int, isPhone: Boolean = false): PermissionGroupUsage = PermissionGroupUsage( "", UserHandle.getUid(user, 0), 0, "", true, isPhone, null, null, null, "", ) private fun createUserInfo(userId: Int) = UserInfo(userId, "", 0) private fun setPrivacyController(micCamera: Boolean, location: Boolean) { whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera) whenever(privacyItemController.locationAvailable).thenReturn(location) } companion object { const val USER_ID = 0 const val PROFILE_USER_ID = 1 const val OTHER_USER_ID = 2 } }
packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt +75 −51 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager import android.safetycenter.SafetyCenterManager Loading @@ -14,6 +15,8 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.appops.AppOpsController import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter Loading @@ -24,14 +27,13 @@ import com.android.systemui.privacy.PrivacyDialogControllerV2 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 java.util.concurrent.Executor import javax.inject.Inject import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.DeviceProvisionedController import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Named interface ChipVisibilityListener { Loading @@ -40,15 +42,17 @@ interface ChipVisibilityListener { /** * Controls privacy icons/chip residing in QS header which show up when app is using camera, * microphone or location. * Manages their visibility depending on privacy signals coming from [PrivacyItemController]. * microphone or location. Manages their visibility depending on privacy signals coming from * [PrivacyItemController]. * * Unlike typical controller extending [com.android.systemui.util.ViewController] this view doesn't * observe its attachment state because depending on where it is used, it might be never detached. * Instead, parent controller should use [onParentVisible] and [onParentInvisible] to "activate" or * "deactivate" this controller. */ class HeaderPrivacyIconsController @Inject constructor( class HeaderPrivacyIconsController @Inject constructor( private val privacyItemController: PrivacyItemController, private val uiEventLogger: UiEventLogger, @Named(SHADE_HEADER) private val privacyChip: OngoingPrivacyChip, Loading @@ -66,6 +70,7 @@ class HeaderPrivacyIconsController @Inject constructor( private val deviceProvisionedController: DeviceProvisionedController, private val featureFlags: FeatureFlags, private val shadeDialogContextInteractor: ShadeDialogContextInteractor, private val userTracker: UserTracker, ) { var chipVisibilityListener: ChipVisibilityListener? = null Loading @@ -80,18 +85,20 @@ class HeaderPrivacyIconsController @Inject constructor( private val dialogContext: Context get() = shadeDialogContextInteractor.context private val safetyCenterReceiver = object : BroadcastReceiver() { private val safetyCenterReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { safetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled() } } val attachStateChangeListener = object : View.OnAttachStateChangeListener { val attachStateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { broadcastDispatcher.registerReceiver( safetyCenterReceiver, IntentFilter(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED), executor = backgroundExecutor executor = backgroundExecutor, ) } Loading @@ -109,7 +116,7 @@ class HeaderPrivacyIconsController @Inject constructor( broadcastDispatcher.registerReceiver( safetyCenterReceiver, IntentFilter(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED), executor = backgroundExecutor executor = backgroundExecutor, ) } Loading Loading @@ -171,22 +178,39 @@ class HeaderPrivacyIconsController @Inject constructor( private fun showSafetyCenter() { backgroundExecutor.execute { val usage = ArrayList(permGroupUsage()) val usage = permGroupUsage() privacyLogger.logUnfilteredPermGroupUsage(usage) val startSafetyCenter = Intent(Intent.ACTION_VIEW_SAFETY_CENTER_QS) startSafetyCenter.putParcelableArrayListExtra(PermissionManager.EXTRA_PERMISSION_USAGES, usage) startSafetyCenter.putParcelableArrayListExtra( PermissionManager.EXTRA_PERMISSION_USAGES, usage, ) startSafetyCenter.flags = Intent.FLAG_ACTIVITY_NEW_TASK uiExecutor.execute { activityStarter.startActivity(startSafetyCenter, true, ActivityTransitionAnimator.Controller.fromView(privacyChip)) activityStarter.startActivity( startSafetyCenter, true, ActivityTransitionAnimator.Controller.fromView(privacyChip), ) } } } @WorkerThread private fun permGroupUsage(): List<PermissionGroupUsage> { return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted) fun permGroupUsage(): ArrayList<PermissionGroupUsage> { val usages = ArrayList(permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)) val invalidUserUsages = mutableListOf<PermissionGroupUsage>() val userProfiles = userTracker.userProfiles for (usage in usages) { val userId = UserHandle.getUserId(usage.uid) if (usage.isPhoneCall || userProfiles.any { it.id == userId }) { continue } invalidUserUsages.add(usage) } usages.removeAll(invalidUserUsages) return usages } fun onParentInvisible() { Loading