diff options
4 files changed, 140 insertions, 45 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 9708d9a02edc..c28a9ebb98e9 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -20,6 +20,7 @@ import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -33,6 +34,7 @@ import android.os.UserHandle; import android.permission.PermissionManager; import android.util.ArraySet; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import androidx.annotation.WorkerThread; @@ -96,19 +98,58 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid = new SparseArray<>(); - protected static final int[] OPS = new int[] { - AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, - AppOpsManager.OP_CAMERA, - AppOpsManager.OP_PHONE_CALL_CAMERA, - AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + @VisibleForTesting + protected static final int[] OPS_MIC = new int[] { AppOpsManager.OP_RECORD_AUDIO, - AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, - AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, AppOpsManager.OP_PHONE_CALL_MICROPHONE, + AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, + AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + }; + + protected static final int[] OPS_CAMERA = new int[] { + AppOpsManager.OP_CAMERA, + AppOpsManager.OP_PHONE_CALL_CAMERA + }; + + protected static final int[] OPS_LOC = new int[] { + AppOpsManager.OP_FINE_LOCATION, AppOpsManager.OP_COARSE_LOCATION, - AppOpsManager.OP_FINE_LOCATION + AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION }; + protected static final int[] OPS_OTHERS = new int[] { + AppOpsManager.OP_SYSTEM_ALERT_WINDOW, + AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO + }; + + + protected static final int[] OPS = concatOps(OPS_MIC, OPS_CAMERA, OPS_LOC, OPS_OTHERS); + + /** + * @param opArrays the given op arrays. + * @return the concatenations of the given op arrays. Null arrays are treated as empty. + */ + private static int[] concatOps(@Nullable int[]...opArrays) { + if (opArrays == null) { + return new int[0]; + } + int totalLength = 0; + for (int[] opArray : opArrays) { + if (opArray == null || opArray.length == 0) { + continue; + } + totalLength += opArray.length; + } + final int[] concatOps = new int[totalLength]; + int index = 0; + for (int[] opArray : opArrays) { + if (opArray == null || opArray.length == 0) continue; + System.arraycopy(opArray, 0, concatOps, index, opArray.length); + index += opArray.length; + } + return concatOps; + } + @Inject public AppOpsControllerImpl( Context context, @@ -533,12 +574,17 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon } private boolean isOpCamera(int op) { - return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA; + for (int i = 0; i < OPS_CAMERA.length; i++) { + if (op == OPS_CAMERA[i]) return true; + } + return false; } private boolean isOpMicrophone(int op) { - return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE - || op == AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; + for (int i = 0; i < OPS_MIC.length; i++) { + if (op == OPS_MIC[i]) return true; + } + return false; } protected class H extends Handler { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt index 88b8676ae1c0..fedbdec113f8 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt @@ -53,11 +53,14 @@ class AppOpsPrivacyItemMonitor @Inject constructor( @VisibleForTesting companion object { - val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA, - AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO, + val OPS_MIC_CAMERA = intArrayOf( + AppOpsManager.OP_CAMERA, + AppOpsManager.OP_PHONE_CALL_CAMERA, + AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, - AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO) + AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, + AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO) val OPS_LOCATION = intArrayOf( AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_FINE_LOCATION) @@ -212,6 +215,7 @@ class AppOpsPrivacyItemMonitor @Inject constructor( AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, + AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE else -> return null } 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 61a651234e0c..b23f7f2db01e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -19,6 +19,8 @@ package com.android.systemui.appops; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; +import static com.android.systemui.appops.AppOpsControllerImpl.OPS_MIC; + import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; @@ -171,6 +173,28 @@ public class AppOpsControllerTest extends SysuiTestCase { TEST_UID, TEST_PACKAGE_NAME, true); } + + // Only the app ops in the {@link com.android.systemui.appops.AppOpsControllerImpl.OPS} will be + // supported by the {@link AppOpsControllerImpl} to add callbacks. The state changes of active + // app ops will be notified by the callback. + @Test + public void addCallback_partialIncludedCode() { + mController.addCallback(new int[]{AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, + AppOpsManager.OP_FINE_LOCATION}, mCallback); + mController.onOpActiveChanged( + AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + mController.onOpActiveChanged( + AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID, TEST_PACKAGE_NAME, + true); + assertEquals(2, mController.getActiveAppOps().size()); + + mTestableLooper.processAllMessages(); + verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, + TEST_UID, TEST_PACKAGE_NAME, true); + verify(mCallback, never()).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO, + TEST_UID, TEST_PACKAGE_NAME, true); + } + @Test public void addCallback_notIncludedCode() { mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); @@ -504,41 +528,17 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test - public void testUnpausedRecordingSentActive() { - mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); - mTestableLooper.processAllMessages(); - mController.onOpActiveChanged( - AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); - - mTestableLooper.processAllMessages(); - mRecordingCallback.onRecordingConfigChanged(Collections.emptyList()); - - mTestableLooper.processAllMessages(); - - verify(mCallback).onActiveStateChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + public void testUnPausedRecordingSentActive() { + for (int i = 0; i < OPS_MIC.length; i++) { + verifyUnPausedSentActive(OPS_MIC[i]); + } } @Test public void testAudioPausedSentInactive() { - mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); - mTestableLooper.processAllMessages(); - mController.onOpActiveChanged( - AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); - mTestableLooper.processAllMessages(); - - AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class); - when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER); - when(mockARC.isClientSilenced()).thenReturn(true); - - mRecordingCallback.onRecordingConfigChanged(List.of(mockARC)); - mTestableLooper.processAllMessages(); - - InOrder inOrder = inOrder(mCallback); - inOrder.verify(mCallback).onActiveStateChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); - inOrder.verify(mCallback).onActiveStateChanged( - AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false); + for (int i = 0; i < OPS_MIC.length; i++) { + verifyAudioPausedSentInactive(OPS_MIC[i]); + } } @Test @@ -673,6 +673,41 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode()); } + private void verifyUnPausedSentActive(int micOpCode) { + mController.addCallback(new int[]{micOpCode}, mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID, + TEST_PACKAGE_NAME, true); + + mTestableLooper.processAllMessages(); + mRecordingCallback.onRecordingConfigChanged(Collections.emptyList()); + + mTestableLooper.processAllMessages(); + + verify(mCallback).onActiveStateChanged(micOpCode, TEST_UID, TEST_PACKAGE_NAME, true); + } + + private void verifyAudioPausedSentInactive(int micOpCode) { + mController.addCallback(new int[]{micOpCode}, mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID_OTHER, + TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + + AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class); + when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER); + when(mockARC.isClientSilenced()).thenReturn(true); + + mRecordingCallback.onRecordingConfigChanged(List.of(mockARC)); + mTestableLooper.processAllMessages(); + + InOrder inOrder = inOrder(mCallback); + inOrder.verify(mCallback).onActiveStateChanged( + micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + inOrder.verify(mCallback).onActiveStateChanged( + micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, false); + } + private class TestHandler extends AppOpsControllerImpl.H { TestHandler(Looper looper) { mController.super(looper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt index db96d5507ba1..14ecf93a134e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt @@ -143,6 +143,16 @@ class AppOpsPrivacyItemMonitorTest : SysuiTestCase() { } @Test + fun testVoiceActivationPrivacyItems() { + doReturn(listOf(AppOpItem(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID, + TEST_PACKAGE_NAME, 0))) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) + val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems() + assertEquals(1, privacyItems.size) + assertEquals(PrivacyType.TYPE_MICROPHONE, privacyItems[0].privacyType) + } + + @Test fun testSimilarItemsDifferentTimeStamp() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 1))) |