Loading packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java +6 −7 Original line number Diff line number Diff line Loading @@ -26,8 +26,7 @@ public class AppOpItem { private String mPackageName; private long mTimeStarted; private StringBuilder mState; // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO private boolean mSilenced; private boolean mIsDisabled; public AppOpItem(int code, int uid, String packageName, long timeStarted) { this.mCode = code; Loading Loading @@ -58,16 +57,16 @@ public class AppOpItem { return mTimeStarted; } public void setSilenced(boolean silenced) { mSilenced = silenced; public void setDisabled(boolean misDisabled) { this.mIsDisabled = misDisabled; } public boolean isSilenced() { return mSilenced; public boolean isDisabled() { return mIsDisabled; } @Override public String toString() { return mState.append(mSilenced).append(")").toString(); return mState.append(mIsDisabled).append(")").toString(); } } packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +56 −20 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.appops; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; import android.Manifest; Loading Loading @@ -45,6 +47,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.util.Assert; import java.io.FileDescriptor; Loading @@ -64,7 +67,8 @@ import javax.inject.Inject; @SysUISingleton public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController, AppOpsManager.OnOpActiveChangedInternalListener, AppOpsManager.OnOpNotedListener, Dumpable { AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback, Dumpable { // This is the minimum time that we will keep AppOps that are noted on record. If multiple // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be Loading @@ -77,8 +81,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final AppOpsManager mAppOps; private final AudioManager mAudioManager; private final LocationManager mLocationManager; // TODO ntmyren: remove t private final PackageManager mPackageManager; private final IndividualSensorPrivacyController mSensorPrivacyController; // mLocationProviderPackages are cached and updated only occasionally private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000; Loading @@ -91,6 +95,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final PermissionFlagsCache mFlagsCache; private boolean mListening; private boolean mMicMuted; private boolean mCameraDisabled; @GuardedBy("mActiveItems") private final List<AppOpItem> mActiveItems = new ArrayList<>(); Loading Loading @@ -118,6 +123,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon DumpManager dumpManager, PermissionFlagsCache cache, AudioManager audioManager, IndividualSensorPrivacyController sensorPrivacyController, BroadcastDispatcher dispatcher ) { mDispatcher = dispatcher; Loading @@ -129,7 +135,10 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mCallbacksByCode.put(OPS[i], new ArraySet<>()); } mAudioManager = audioManager; mMicMuted = audioManager.isMicrophoneMute(); mSensorPrivacyController = sensorPrivacyController; mMicMuted = audioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mPackageManager = context.getPackageManager(); dumpManager.registerDumpable(TAG, this); Loading @@ -147,6 +156,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAppOps.startWatchingActive(OPS, this); mAppOps.startWatchingNoted(OPS, this); mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler); mSensorPrivacyController.addCallback(this); mMicMuted = mAudioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); mDispatcher.registerReceiverWithHandler(this, Loading @@ -156,6 +171,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAppOps.stopWatchingActive(this); mAppOps.stopWatchingNoted(this); mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); mSensorPrivacyController.removeCallback(this); mBGHandler.removeCallbacksAndMessages(null); // null removes all mDispatcher.unregisterReceiver(this); Loading Loading @@ -235,11 +251,13 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon if (item == null && active) { item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); if (code == AppOpsManager.OP_RECORD_AUDIO) { item.setSilenced(isAnyRecordingPausedLocked(uid)); item.setDisabled(isAnyRecordingPausedLocked(uid)); } else if (code == AppOpsManager.OP_CAMERA) { item.setDisabled(mCameraDisabled); } mActiveItems.add(item); if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); return !item.isSilenced(); return !item.isDisabled(); } else if (item != null && !active) { mActiveItems.remove(item); if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); Loading Loading @@ -409,7 +427,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) && isUserVisible(item) && !item.isSilenced()) { && isUserVisible(item) && !item.isDisabled()) { list.add(item); } } Loading Loading @@ -512,26 +530,31 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return false; } private void updateRecordingPausedStatus() { private void updateSensorDisabledStatus() { synchronized (mActiveItems) { int size = mActiveItems.size(); for (int i = 0; i < size; i++) { AppOpItem item = mActiveItems.get(i); boolean paused = false; if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) { boolean paused = isAnyRecordingPausedLocked(item.getUid()); if (item.isSilenced() != paused) { item.setSilenced(paused); paused = isAnyRecordingPausedLocked(item.getUid()); } else if (item.getCode() == AppOpsManager.OP_CAMERA) { paused = mCameraDisabled; } if (item.isDisabled() != paused) { item.setDisabled(paused); notifySuscribers( item.getCode(), item.getUid(), item.getPackageName(), !item.isSilenced() !item.isDisabled() ); } } } } } private AudioManager.AudioRecordingCallback mAudioRecordingCallback = new AudioManager.AudioRecordingCallback() { Loading @@ -552,14 +575,27 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon recordings.add(recording); } } updateRecordingPausedStatus(); updateSensorDisabledStatus(); } }; @Override public void onReceive(Context context, Intent intent) { mMicMuted = mAudioManager.isMicrophoneMute(); updateRecordingPausedStatus(); mMicMuted = mAudioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); updateSensorDisabledStatus(); } @Override public void onSensorBlockedChanged(int sensor, boolean blocked) { mBGHandler.post(() -> { if (sensor == INDIVIDUAL_SENSOR_CAMERA) { mCameraDisabled = blocked; } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) { mMicMuted = mAudioManager.isMicrophoneMute() || blocked; } updateSensorDisabledStatus(); }); } protected class H extends Handler { Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +4 −3 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.policy; import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.IndividualSensor; Loading @@ -30,7 +30,8 @@ import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE}; private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA, INDIVIDUAL_SENSOR_MICROPHONE}; private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mState = new SparseBooleanArray(); Loading packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +79 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.appops; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; Loading Loading @@ -49,6 +52,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import org.junit.Before; import org.junit.Test; Loading Loading @@ -81,6 +85,8 @@ public class AppOpsControllerTest extends SysuiTestCase { private PermissionFlagsCache mFlagsCache; @Mock private PackageManager mPackageManager; @Mock private IndividualSensorPrivacyController mSensorPrivacyController; @Mock(stubOnly = true) private AudioManager mAudioManager; @Mock() Loading Loading @@ -118,12 +124,18 @@ public class AppOpsControllerTest extends SysuiTestCase { when(mAudioManager.getActiveRecordingConfigurations()) .thenReturn(List.of(mPausedMockRecording)); when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) .thenReturn(false); when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) .thenReturn(false); mController = new AppOpsControllerImpl( mContext, mTestableLooper.getLooper(), mDumpManager, mFlagsCache, mAudioManager, mSensorPrivacyController, mDispatcher ); } Loading @@ -133,6 +145,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setListening(true); verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController); verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any()); verify(mSensorPrivacyController, times(1)).addCallback(mController); } @Test Loading @@ -140,6 +153,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setListening(false); verify(mAppOpsManager, times(1)).stopWatchingActive(mController); verify(mDispatcher, times(1)).unregisterReceiver(mController); verify(mSensorPrivacyController, times(1)).removeCallback(mController); } @Test Loading Loading @@ -476,6 +490,71 @@ public class AppOpsControllerTest extends SysuiTestCase { AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false); } @Test public void testAudioFilteredWhenMicDisabled() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); assertEquals(AppOpsManager.OP_RECORD_AUDIO, 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(INDIVIDUAL_SENSOR_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(INDIVIDUAL_SENSOR_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_RECORD_AUDIO, list.get(micIdx).getCode()); } @Test public void testCameraFilteredWhenCameraDisabled() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); assertEquals(AppOpsManager.OP_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(INDIVIDUAL_SENSOR_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(INDIVIDUAL_SENSOR_CAMERA, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); assertEquals(2, list.size()); int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1; assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode()); } private class TestHandler extends AppOpsControllerImpl.H { TestHandler(Looper looper) { mController.super(looper); Loading Loading
packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java +6 −7 Original line number Diff line number Diff line Loading @@ -26,8 +26,7 @@ public class AppOpItem { private String mPackageName; private long mTimeStarted; private StringBuilder mState; // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO private boolean mSilenced; private boolean mIsDisabled; public AppOpItem(int code, int uid, String packageName, long timeStarted) { this.mCode = code; Loading Loading @@ -58,16 +57,16 @@ public class AppOpItem { return mTimeStarted; } public void setSilenced(boolean silenced) { mSilenced = silenced; public void setDisabled(boolean misDisabled) { this.mIsDisabled = misDisabled; } public boolean isSilenced() { return mSilenced; public boolean isDisabled() { return mIsDisabled; } @Override public String toString() { return mState.append(mSilenced).append(")").toString(); return mState.append(mIsDisabled).append(")").toString(); } }
packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +56 −20 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.appops; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; import android.Manifest; Loading Loading @@ -45,6 +47,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.util.Assert; import java.io.FileDescriptor; Loading @@ -64,7 +67,8 @@ import javax.inject.Inject; @SysUISingleton public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController, AppOpsManager.OnOpActiveChangedInternalListener, AppOpsManager.OnOpNotedListener, Dumpable { AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback, Dumpable { // This is the minimum time that we will keep AppOps that are noted on record. If multiple // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be Loading @@ -77,8 +81,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final AppOpsManager mAppOps; private final AudioManager mAudioManager; private final LocationManager mLocationManager; // TODO ntmyren: remove t private final PackageManager mPackageManager; private final IndividualSensorPrivacyController mSensorPrivacyController; // mLocationProviderPackages are cached and updated only occasionally private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000; Loading @@ -91,6 +95,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final PermissionFlagsCache mFlagsCache; private boolean mListening; private boolean mMicMuted; private boolean mCameraDisabled; @GuardedBy("mActiveItems") private final List<AppOpItem> mActiveItems = new ArrayList<>(); Loading Loading @@ -118,6 +123,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon DumpManager dumpManager, PermissionFlagsCache cache, AudioManager audioManager, IndividualSensorPrivacyController sensorPrivacyController, BroadcastDispatcher dispatcher ) { mDispatcher = dispatcher; Loading @@ -129,7 +135,10 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mCallbacksByCode.put(OPS[i], new ArraySet<>()); } mAudioManager = audioManager; mMicMuted = audioManager.isMicrophoneMute(); mSensorPrivacyController = sensorPrivacyController; mMicMuted = audioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mPackageManager = context.getPackageManager(); dumpManager.registerDumpable(TAG, this); Loading @@ -147,6 +156,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAppOps.startWatchingActive(OPS, this); mAppOps.startWatchingNoted(OPS, this); mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler); mSensorPrivacyController.addCallback(this); mMicMuted = mAudioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); mDispatcher.registerReceiverWithHandler(this, Loading @@ -156,6 +171,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAppOps.stopWatchingActive(this); mAppOps.stopWatchingNoted(this); mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); mSensorPrivacyController.removeCallback(this); mBGHandler.removeCallbacksAndMessages(null); // null removes all mDispatcher.unregisterReceiver(this); Loading Loading @@ -235,11 +251,13 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon if (item == null && active) { item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); if (code == AppOpsManager.OP_RECORD_AUDIO) { item.setSilenced(isAnyRecordingPausedLocked(uid)); item.setDisabled(isAnyRecordingPausedLocked(uid)); } else if (code == AppOpsManager.OP_CAMERA) { item.setDisabled(mCameraDisabled); } mActiveItems.add(item); if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); return !item.isSilenced(); return !item.isDisabled(); } else if (item != null && !active) { mActiveItems.remove(item); if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); Loading Loading @@ -409,7 +427,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) && isUserVisible(item) && !item.isSilenced()) { && isUserVisible(item) && !item.isDisabled()) { list.add(item); } } Loading Loading @@ -512,26 +530,31 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return false; } private void updateRecordingPausedStatus() { private void updateSensorDisabledStatus() { synchronized (mActiveItems) { int size = mActiveItems.size(); for (int i = 0; i < size; i++) { AppOpItem item = mActiveItems.get(i); boolean paused = false; if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) { boolean paused = isAnyRecordingPausedLocked(item.getUid()); if (item.isSilenced() != paused) { item.setSilenced(paused); paused = isAnyRecordingPausedLocked(item.getUid()); } else if (item.getCode() == AppOpsManager.OP_CAMERA) { paused = mCameraDisabled; } if (item.isDisabled() != paused) { item.setDisabled(paused); notifySuscribers( item.getCode(), item.getUid(), item.getPackageName(), !item.isSilenced() !item.isDisabled() ); } } } } } private AudioManager.AudioRecordingCallback mAudioRecordingCallback = new AudioManager.AudioRecordingCallback() { Loading @@ -552,14 +575,27 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon recordings.add(recording); } } updateRecordingPausedStatus(); updateSensorDisabledStatus(); } }; @Override public void onReceive(Context context, Intent intent) { mMicMuted = mAudioManager.isMicrophoneMute(); updateRecordingPausedStatus(); mMicMuted = mAudioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); updateSensorDisabledStatus(); } @Override public void onSensorBlockedChanged(int sensor, boolean blocked) { mBGHandler.post(() -> { if (sensor == INDIVIDUAL_SENSOR_CAMERA) { mCameraDisabled = blocked; } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) { mMicMuted = mAudioManager.isMicrophoneMute() || blocked; } updateSensorDisabledStatus(); }); } protected class H extends Handler { Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +4 −3 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.policy; import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.IndividualSensor; Loading @@ -30,7 +30,8 @@ import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE}; private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA, INDIVIDUAL_SENSOR_MICROPHONE}; private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mState = new SparseBooleanArray(); Loading
packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +79 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package com.android.systemui.appops; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; Loading Loading @@ -49,6 +52,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import org.junit.Before; import org.junit.Test; Loading Loading @@ -81,6 +85,8 @@ public class AppOpsControllerTest extends SysuiTestCase { private PermissionFlagsCache mFlagsCache; @Mock private PackageManager mPackageManager; @Mock private IndividualSensorPrivacyController mSensorPrivacyController; @Mock(stubOnly = true) private AudioManager mAudioManager; @Mock() Loading Loading @@ -118,12 +124,18 @@ public class AppOpsControllerTest extends SysuiTestCase { when(mAudioManager.getActiveRecordingConfigurations()) .thenReturn(List.of(mPausedMockRecording)); when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) .thenReturn(false); when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) .thenReturn(false); mController = new AppOpsControllerImpl( mContext, mTestableLooper.getLooper(), mDumpManager, mFlagsCache, mAudioManager, mSensorPrivacyController, mDispatcher ); } Loading @@ -133,6 +145,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setListening(true); verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController); verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any()); verify(mSensorPrivacyController, times(1)).addCallback(mController); } @Test Loading @@ -140,6 +153,7 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.setListening(false); verify(mAppOpsManager, times(1)).stopWatchingActive(mController); verify(mDispatcher, times(1)).unregisterReceiver(mController); verify(mSensorPrivacyController, times(1)).removeCallback(mController); } @Test Loading Loading @@ -476,6 +490,71 @@ public class AppOpsControllerTest extends SysuiTestCase { AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false); } @Test public void testAudioFilteredWhenMicDisabled() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); assertEquals(AppOpsManager.OP_RECORD_AUDIO, 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(INDIVIDUAL_SENSOR_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(INDIVIDUAL_SENSOR_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_RECORD_AUDIO, list.get(micIdx).getCode()); } @Test public void testCameraFilteredWhenCameraDisabled() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, mCallback); mTestableLooper.processAllMessages(); mController.onOpActiveChanged( AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); List<AppOpItem> list = mController.getActiveAppOps(); assertEquals(1, list.size()); assertEquals(AppOpsManager.OP_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(INDIVIDUAL_SENSOR_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(INDIVIDUAL_SENSOR_CAMERA, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); assertEquals(2, list.size()); int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1; assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode()); } private class TestHandler extends AppOpsControllerImpl.H { TestHandler(Looper looper) { mController.super(looper); Loading