From 60864b944e4b40a5fb7644bf9855b8a96015b5b6 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Mon, 12 Apr 2021 11:47:15 -0400 Subject: [PATCH] Do not hold indicators for apps that become paused If an app op becomes "paused" (microphone muted or disabled by toggle), remove the indicator immediately as opposed to holding for 5s. Also, pass the value that we are using for mic muted to PermissionManager, so they are in sync. Test: atest SystemUITests Test: manual Fixes: 184891081 Change-Id: I4d46fc6e1cefa45c0d718cc01f40c8f060dafee7 --- core/api/test-current.txt | 1 + .../android/permission/PermissionManager.java | 18 ++++ .../systemui/appops/AppOpsController.java | 18 +++- .../systemui/appops/AppOpsControllerImpl.java | 35 +++++-- .../privacy/PrivacyDialogController.kt | 13 ++- .../android/systemui/privacy/PrivacyItem.kt | 3 +- .../systemui/privacy/PrivacyItemController.kt | 21 ++++- .../systemui/appops/AppOpsControllerTest.java | 91 +++++++++++++++++-- .../privacy/PrivacyDialogControllerTest.kt | 47 +++++++--- .../privacy/PrivacyItemControllerTest.kt | 66 +++++++++----- 10 files changed, 252 insertions(+), 61 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0541934fd6655..17578266b51ef 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2009,6 +2009,7 @@ package android.permission { public final class PermissionManager { method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List getIndicatorAppOpUsageData(); + method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List getIndicatorAppOpUsageData(boolean); method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource); } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 1a40f0640a8d7..17c90d64ce6a9 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -885,6 +885,24 @@ public final class PermissionManager { return mUsageHelper.getOpUsageData(new AudioManager().isMicrophoneMute()); } + /** + * @param micMuted whether to consider the microphone muted when retrieving audio ops + * @return A list of permission groups currently or recently used by all apps by all users in + * the current profile group. + * + * @hide + */ + @TestApi + @NonNull + @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) + public List getIndicatorAppOpUsageData(boolean micMuted) { + // Lazily initialize the usage helper + if (mUsageHelper == null) { + mUsageHelper = new PermissionUsageHelper(mContext); + } + return mUsageHelper.getOpUsageData(micMuted); + } + /** * Determine if a package should be shown in indicators. Only a select few roles, and the * system app itself, are hidden. These values are updated at most every 15 seconds. diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java index 2661d8913a3fd..b544599d0979f 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java @@ -60,17 +60,31 @@ public interface AppOpsController { /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * - * @return List of active AppOps information + * @return List of active AppOps information, without paused elements. */ List getActiveAppOps(); + /** + * Returns a copy of the list containing all the active AppOps that the controller tracks. + * + * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise. + * @return List of active AppOps information + */ + List getActiveAppOps(boolean showPaused); + /** * Returns a copy of the list containing all the active AppOps that the controller tracks, for * a given user id. * * @param userId User id to track + * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise. * * @return List of active AppOps information for that user id */ - List getActiveAppOpsForUser(int userId); + List getActiveAppOpsForUser(int userId, boolean showPaused); + + /** + * @return whether this controller is considering the microphone as muted. + */ + boolean isMicMuted(); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 994401db43801..534f93ec0e473 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -241,9 +241,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName); if (item == null && active) { item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime()); - if (code == AppOpsManager.OP_RECORD_AUDIO) { + if (isOpMicrophone(code)) { item.setDisabled(isAnyRecordingPausedLocked(uid)); - } else if (code == AppOpsManager.OP_CAMERA) { + } else if (isOpCamera(code)) { item.setDisabled(mCameraDisabled); } mActiveItems.add(item); @@ -298,6 +298,11 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); } + @WorkerThread + public List getActiveAppOps() { + return getActiveAppOps(false); + } + /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * @@ -306,8 +311,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon * @return List of active AppOps information */ @WorkerThread - public List getActiveAppOps() { - return getActiveAppOpsForUser(UserHandle.USER_ALL); + public List getActiveAppOps(boolean showPaused) { + return getActiveAppOpsForUser(UserHandle.USER_ALL, showPaused); } /** @@ -321,7 +326,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon * @return List of active AppOps information for that user id */ @WorkerThread - public List getActiveAppOpsForUser(int userId) { + public List getActiveAppOpsForUser(int userId, boolean showPaused) { Assert.isNotMainThread(); List list = new ArrayList<>(); synchronized (mActiveItems) { @@ -330,7 +335,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item.getPackageName()) && !item.isDisabled()) { + && isUserVisible(item.getPackageName()) + && (showPaused || !item.isDisabled())) { list.add(item); } } @@ -441,9 +447,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); boolean paused = false; - if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) { + if (isOpMicrophone(item.getCode())) { paused = isAnyRecordingPausedLocked(item.getUid()); - } else if (item.getCode() == AppOpsManager.OP_CAMERA) { + } else if (isOpCamera(item.getCode())) { paused = mCameraDisabled; } @@ -502,6 +508,19 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon }); } + @Override + public boolean isMicMuted() { + return mMicMuted; + } + + private boolean isOpCamera(int op) { + return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA; + } + + private boolean isOpMicrophone(int op) { + return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE; + } + protected class H extends Handler { H(Looper looper) { super(looper); diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt index f87ea7c61ca8b..feb27d804e5e2 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt @@ -29,6 +29,7 @@ import android.util.Log import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.android.systemui.appops.AppOpsController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -48,7 +49,6 @@ private val defaultDialogProvider = object : PrivacyDialogController.DialogProvi return PrivacyDialog(context, list, starter) } } - /** * Controller for [PrivacyDialog]. * @@ -66,6 +66,7 @@ class PrivacyDialogController( private val uiExecutor: Executor, private val privacyLogger: PrivacyLogger, private val keyguardStateController: KeyguardStateController, + private val appOpsController: AppOpsController, @VisibleForTesting private val dialogProvider: DialogProvider ) { @@ -79,7 +80,8 @@ class PrivacyDialogController( @Background backgroundExecutor: Executor, @Main uiExecutor: Executor, privacyLogger: PrivacyLogger, - keyguardStateController: KeyguardStateController + keyguardStateController: KeyguardStateController, + appOpsController: AppOpsController ) : this( permissionManager, packageManager, @@ -90,6 +92,7 @@ class PrivacyDialogController( uiExecutor, privacyLogger, keyguardStateController, + appOpsController, defaultDialogProvider ) @@ -127,7 +130,9 @@ class PrivacyDialogController( } @WorkerThread - private fun permGroupUsage(): List = permissionManager.indicatorAppOpUsageData + private fun permGroupUsage(): List { + return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted) + } /** * Show the [PrivacyDialog] @@ -261,4 +266,4 @@ class PrivacyDialogController( starter: (String, Int) -> Unit ): PrivacyDialog } -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt index 4c617edd8542c..63ec6e5db017d 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt @@ -54,7 +54,8 @@ private const val UNKNOWN_TIMESTAMP = -1L data class PrivacyItem( val privacyType: PrivacyType, val application: PrivacyApplication, - val timeStampElapsed: Long = UNKNOWN_TIMESTAMP + val timeStampElapsed: Long = UNKNOWN_TIMESTAMP, + val paused: Boolean = false ) { val log = "(${privacyType.logName}, ${application.packageName}(${application.uid}), " + "$timeStampElapsed)" diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index f7e2a31ce727a..8b27b6ecbfd19 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -257,7 +257,7 @@ class PrivacyItemController @Inject constructor( privacyList = emptyList() return } - val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter { + val list = appOpsController.getActiveAppOps(true).filter { UserHandle.getUserId(it.uid) in currentUserIds || it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE || it.code == AppOpsManager.OP_PHONE_CALL_CAMERA @@ -279,7 +279,9 @@ class PrivacyItemController @Inject constructor( // Anything earlier than this timestamp can be removed val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS - val mustKeep = privacyList.filter { it.timeStampElapsed > removeBeforeTime && it !in list } + val mustKeep = privacyList.filter { + it.timeStampElapsed > removeBeforeTime && !(it isIn list) + } // There are items we must keep because they haven't been around for enough time. if (mustKeep.isNotEmpty()) { @@ -291,7 +293,18 @@ class PrivacyItemController @Inject constructor( logger.logPrivacyItemsUpdateScheduled(delay) holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay) } - return list + mustKeep + return list.filter { !it.paused } + mustKeep + } + + /** + * Ignores the paused status to determine if the element is in the list + */ + private infix fun PrivacyItem.isIn(list: List): Boolean { + return list.any { + it.privacyType == privacyType && + it.application == application && + it.timeStampElapsed == timeStampElapsed + } } private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? { @@ -308,7 +321,7 @@ class PrivacyItemController @Inject constructor( return null } val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid) - return PrivacyItem(type, app, appOpItem.timeStartedElapsed) + return PrivacyItem(type, app, appOpItem.timeStartedElapsed, appOpItem.isDisabled) } interface Callback { diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 3f0831cadd1f0..78c67170d185d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -233,9 +233,9 @@ public class AppOpsControllerTest extends SysuiTestCase { TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); assertEquals(2, - mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size()); - assertEquals(1, - mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size()); + mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID), false).size()); + assertEquals(1, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER), + false).size()); } @Test @@ -245,11 +245,11 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true); assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); + UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true); assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); + UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); } @Test @@ -441,7 +441,19 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test - public void testOnlyRecordAudioPaused() { + public void testPausedPhoneCallMicrophoneFilteredOut() { + mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback); + mTestableLooper.processAllMessages(); + + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + + assertTrue(mController.getActiveAppOps().isEmpty()); + } + + @Test + public void testOnlyRecordAudioPhoneCallMicrophonePaused() { mController.addCallback(new int[]{ AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA @@ -531,6 +543,40 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(micIdx).getCode()); } + @Test + public void testPhoneCallMicrophoneFilteredWhenMicDisabled() { + mController.addCallback( + new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add a camera op, and disable the microphone. The camera op should be the only op returned + mController.onSensorBlockedChanged(MICROPHONE, true); + mController.onOpActiveChanged( + AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode()); + + + // Re enable the microphone, and verify the op returns + mController.onSensorBlockedChanged(MICROPHONE, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0; + assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(micIdx).getCode()); + } + @Test public void testCameraFilteredWhenCameraDisabled() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, @@ -563,6 +609,39 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode()); } + @Test + public void testPhoneCallCameraFilteredWhenCameraDisabled() { + mController.addCallback( + new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add an audio op, and disable the camera. The audio op should be the only op returned + mController.onSensorBlockedChanged(CAMERA, true); + mController.onOpActiveChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); + + // Re enable the camera, and verify the op returns + mController.onSensorBlockedChanged(CAMERA, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_PHONE_CALL_CAMERA ? 0 : 1; + assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode()); + } + private class TestHandler extends AppOpsControllerImpl.H { TestHandler(Looper looper) { mController.super(looper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt index 791dd121852f1..05a1e4ff474da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt @@ -28,6 +28,7 @@ import android.permission.PermissionManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.appops.AppOpsController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker @@ -43,6 +44,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor @@ -86,6 +88,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { private lateinit var privacyLogger: PrivacyLogger @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var appOpsController: AppOpsController @Captor private lateinit var dialogDismissedCaptor: ArgumentCaptor @Captor @@ -131,6 +135,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { uiExecutor, privacyLogger, keyguardStateController, + appOpsController, dialogProvider ) } @@ -142,19 +147,28 @@ class PrivacyDialogControllerTest : SysuiTestCase() { dialogProvider.starter = null } + @Test + fun testMicMutedParameter() { + `when`(appOpsController.isMicMuted).thenReturn(true) + controller.showDialog(context) + backgroundExecutor.runAllReady() + + verify(permissionManager).getIndicatorAppOpUsageData(true) + } + @Test fun testPermissionManagerOnlyCalledInBackgroundThread() { controller.showDialog(context) - verify(permissionManager, never()).indicatorAppOpUsageData + verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean()) backgroundExecutor.runAllReady() - verify(permissionManager).indicatorAppOpUsageData + verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean()) } @Test fun testPackageManagerOnlyCalledInBackgroundThread() { val usage = createMockPermGroupUsage() `when`(usage.isPhoneCall).thenReturn(false) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) @@ -217,7 +231,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isPhoneCall = false, attribution = TEST_ATTRIBUTION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) exhaustExecutors() @@ -246,7 +260,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { packageName = "${TEST_PACKAGE_NAME}_microphone", permGroupName = PERM_MICROPHONE ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_microphone, usage_camera) ) @@ -269,7 +283,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_recent, usage_active) ) @@ -292,7 +306,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isActive = true, lastAccess = 1L ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_active, usage_active_moreRecent) ) controller.showDialog(context) @@ -319,7 +333,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isActive = false, lastAccess = 2L ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_recent, usage_mostRecent, usage_moreRecent) ) @@ -342,7 +356,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(false) @@ -366,7 +380,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.locationAvailable).thenReturn(false) @@ -392,7 +406,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(true) @@ -416,7 +430,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(false) @@ -433,7 +447,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { val usage_enterprise = createMockPermGroupUsage( uid = generateUidForUser(ENT_USER_ID) ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_enterprise)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_enterprise)) controller.showDialog(context) exhaustExecutors() @@ -446,7 +461,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { val usage_other = createMockPermGroupUsage( uid = generateUidForUser(ENT_USER_ID + 1) ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_other)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_other)) controller.showDialog(context) exhaustExecutors() @@ -514,7 +530,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { } private fun setUpDefaultMockResponses() { - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(emptyList()) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList()) + `when`(appOpsController.isMicMuted).thenReturn(false) `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) .thenAnswer { diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index bba1c6a00675c..e4d7b1b7d4518 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -46,7 +46,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyList import org.mockito.Captor import org.mockito.Mock @@ -156,7 +156,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testDistinctItems() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -168,7 +168,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testSimilarItemsDifferentTimeStamp() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -215,7 +215,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testMultipleCallbacksAreUpdated() { - doReturn(emptyList()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList()).`when`(appOpsController).getActiveAppOps(anyBoolean()) val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) @@ -233,7 +233,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testRemoveCallback() { - doReturn(emptyList()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList()).`when`(appOpsController).getActiveAppOps(anyBoolean()) val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) privacyItemController.addCallback(otherCallback) @@ -254,7 +254,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testListShouldNotHaveNull() { doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() executor.runAllReady() @@ -292,7 +292,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -306,7 +306,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testNotUpdated_LocationChangeWhenOnlyMicCamera() { doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) changeLocation(false) @@ -338,7 +338,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testLogListUpdated() { doReturn(listOf( AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -362,10 +362,10 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test - fun testListRequestedForAllUsers() { + fun testListRequestedShowPaused() { privacyItemController.addCallback(callback) executor.runAllReady() - verify(appOpsController).getActiveAppOpsForUser(UserHandle.USER_ALL) + verify(appOpsController).getActiveAppOps(true) } @Test @@ -377,7 +377,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { doReturn(listOf( AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -401,7 +401,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -424,7 +424,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -442,7 +442,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testPassageOfTimeDoesNotRemoveIndicators() { doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) @@ -458,12 +458,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD + 1 - doReturn(emptyList()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -481,12 +481,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 - doReturn(emptyList()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -504,12 +504,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 - doReturn(emptyList()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -525,6 +525,30 @@ class PrivacyItemControllerTest : SysuiTestCase() { assertTrue(privacyItemController.privacyList.isEmpty()) } + @Test + fun testPausedElementsAreRemoved() { + val item = AppOpItem( + AppOpsManager.OP_RECORD_AUDIO, + TEST_UID, + TEST_PACKAGE_NAME, + elapsedTime + ) + `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item)) + privacyItemController.addCallback(callback) + executor.runAllReady() + + item.isDisabled = true + fakeClock.advanceTime(1) + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) + + executor.runAllReady() + + verify(callback).onPrivacyItemsChanged(emptyList()) + assertTrue(privacyItemController.privacyList.isEmpty()) + } + private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value)