Merge "Hide indicators when camera/mic disabled" into sc-dev am: 9eba46e770

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13418589

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I87aee3b0821f2bb9144401ba7391a183c70dcf47
This commit is contained in:
Nate Myren
2021-01-29 17:33:48 +00:00
committed by Automerger Merge Worker
4 changed files with 145 additions and 30 deletions

View File

@@ -26,8 +26,7 @@ public class AppOpItem {
private String mPackageName; private String mPackageName;
private long mTimeStarted; private long mTimeStarted;
private StringBuilder mState; private StringBuilder mState;
// This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO private boolean mIsDisabled;
private boolean mSilenced;
public AppOpItem(int code, int uid, String packageName, long timeStarted) { public AppOpItem(int code, int uid, String packageName, long timeStarted) {
this.mCode = code; this.mCode = code;
@@ -58,16 +57,16 @@ public class AppOpItem {
return mTimeStarted; return mTimeStarted;
} }
public void setSilenced(boolean silenced) { public void setDisabled(boolean misDisabled) {
mSilenced = silenced; this.mIsDisabled = misDisabled;
} }
public boolean isSilenced() { public boolean isDisabled() {
return mSilenced; return mIsDisabled;
} }
@Override @Override
public String toString() { public String toString() {
return mState.append(mSilenced).append(")").toString(); return mState.append(mIsDisabled).append(")").toString();
} }
} }

View File

@@ -16,6 +16,8 @@
package com.android.systemui.appops; 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 static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
import android.Manifest; import android.Manifest;
@@ -45,6 +47,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.util.Assert; import com.android.systemui.util.Assert;
import java.io.FileDescriptor; import java.io.FileDescriptor;
@@ -64,7 +67,8 @@ import javax.inject.Inject;
@SysUISingleton @SysUISingleton
public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController, public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
AppOpsManager.OnOpActiveChangedInternalListener, 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 // 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 // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be
@@ -77,8 +81,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
private final AppOpsManager mAppOps; private final AppOpsManager mAppOps;
private final AudioManager mAudioManager; private final AudioManager mAudioManager;
private final LocationManager mLocationManager; private final LocationManager mLocationManager;
// TODO ntmyren: remove t
private final PackageManager mPackageManager; private final PackageManager mPackageManager;
private final IndividualSensorPrivacyController mSensorPrivacyController;
// mLocationProviderPackages are cached and updated only occasionally // mLocationProviderPackages are cached and updated only occasionally
private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000; private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
@@ -91,6 +95,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
private final PermissionFlagsCache mFlagsCache; private final PermissionFlagsCache mFlagsCache;
private boolean mListening; private boolean mListening;
private boolean mMicMuted; private boolean mMicMuted;
private boolean mCameraDisabled;
@GuardedBy("mActiveItems") @GuardedBy("mActiveItems")
private final List<AppOpItem> mActiveItems = new ArrayList<>(); private final List<AppOpItem> mActiveItems = new ArrayList<>();
@@ -118,6 +123,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
DumpManager dumpManager, DumpManager dumpManager,
PermissionFlagsCache cache, PermissionFlagsCache cache,
AudioManager audioManager, AudioManager audioManager,
IndividualSensorPrivacyController sensorPrivacyController,
BroadcastDispatcher dispatcher BroadcastDispatcher dispatcher
) { ) {
mDispatcher = dispatcher; mDispatcher = dispatcher;
@@ -129,7 +135,10 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
mCallbacksByCode.put(OPS[i], new ArraySet<>()); mCallbacksByCode.put(OPS[i], new ArraySet<>());
} }
mAudioManager = audioManager; 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); mLocationManager = context.getSystemService(LocationManager.class);
mPackageManager = context.getPackageManager(); mPackageManager = context.getPackageManager();
dumpManager.registerDumpable(TAG, this); dumpManager.registerDumpable(TAG, this);
@@ -147,6 +156,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
mAppOps.startWatchingActive(OPS, this); mAppOps.startWatchingActive(OPS, this);
mAppOps.startWatchingNoted(OPS, this); mAppOps.startWatchingNoted(OPS, this);
mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler); 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( mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
mAudioManager.getActiveRecordingConfigurations())); mAudioManager.getActiveRecordingConfigurations()));
mDispatcher.registerReceiverWithHandler(this, mDispatcher.registerReceiverWithHandler(this,
@@ -156,6 +171,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
mAppOps.stopWatchingActive(this); mAppOps.stopWatchingActive(this);
mAppOps.stopWatchingNoted(this); mAppOps.stopWatchingNoted(this);
mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
mSensorPrivacyController.removeCallback(this);
mBGHandler.removeCallbacksAndMessages(null); // null removes all mBGHandler.removeCallbacksAndMessages(null); // null removes all
mDispatcher.unregisterReceiver(this); mDispatcher.unregisterReceiver(this);
@@ -235,11 +251,13 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
if (item == null && active) { if (item == null && active) {
item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
if (code == AppOpsManager.OP_RECORD_AUDIO) { 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); mActiveItems.add(item);
if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
return !item.isSilenced(); return !item.isDisabled();
} else if (item != null && !active) { } else if (item != null && !active) {
mActiveItems.remove(item); mActiveItems.remove(item);
if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -409,7 +427,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
AppOpItem item = mActiveItems.get(i); AppOpItem item = mActiveItems.get(i);
if ((userId == UserHandle.USER_ALL if ((userId == UserHandle.USER_ALL
|| UserHandle.getUserId(item.getUid()) == userId) || UserHandle.getUserId(item.getUid()) == userId)
&& isUserVisible(item) && !item.isSilenced()) { && isUserVisible(item) && !item.isDisabled()) {
list.add(item); list.add(item);
} }
} }
@@ -512,22 +530,27 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
return false; return false;
} }
private void updateRecordingPausedStatus() { private void updateSensorDisabledStatus() {
synchronized (mActiveItems) { synchronized (mActiveItems) {
int size = mActiveItems.size(); int size = mActiveItems.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
AppOpItem item = mActiveItems.get(i); AppOpItem item = mActiveItems.get(i);
boolean paused = false;
if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) { if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
boolean paused = isAnyRecordingPausedLocked(item.getUid()); paused = isAnyRecordingPausedLocked(item.getUid());
if (item.isSilenced() != paused) { } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
item.setSilenced(paused); paused = mCameraDisabled;
notifySuscribers( }
item.getCode(),
item.getUid(), if (item.isDisabled() != paused) {
item.getPackageName(), item.setDisabled(paused);
!item.isSilenced() notifySuscribers(
); item.getCode(),
} item.getUid(),
item.getPackageName(),
!item.isDisabled()
);
} }
} }
} }
@@ -552,14 +575,27 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
recordings.add(recording); recordings.add(recording);
} }
} }
updateRecordingPausedStatus(); updateSensorDisabledStatus();
} }
}; };
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
mMicMuted = mAudioManager.isMicrophoneMute(); mMicMuted = mAudioManager.isMicrophoneMute()
updateRecordingPausedStatus(); || 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 { protected class H extends Handler {

View File

@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.policy; package com.android.systemui.statusbar.policy;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.IndividualSensor; import android.hardware.SensorPrivacyManager.IndividualSensor;
@@ -30,7 +30,8 @@ import java.util.Set;
public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { 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 @NonNull SensorPrivacyManager mSensorPrivacyManager;
private final SparseBooleanArray mState = new SparseBooleanArray(); private final SparseBooleanArray mState = new SparseBooleanArray();

View File

@@ -16,6 +16,9 @@
package com.android.systemui.appops; 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 junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -49,6 +52,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -81,6 +85,8 @@ public class AppOpsControllerTest extends SysuiTestCase {
private PermissionFlagsCache mFlagsCache; private PermissionFlagsCache mFlagsCache;
@Mock @Mock
private PackageManager mPackageManager; private PackageManager mPackageManager;
@Mock
private IndividualSensorPrivacyController mSensorPrivacyController;
@Mock(stubOnly = true) @Mock(stubOnly = true)
private AudioManager mAudioManager; private AudioManager mAudioManager;
@Mock() @Mock()
@@ -118,12 +124,18 @@ public class AppOpsControllerTest extends SysuiTestCase {
when(mAudioManager.getActiveRecordingConfigurations()) when(mAudioManager.getActiveRecordingConfigurations())
.thenReturn(List.of(mPausedMockRecording)); .thenReturn(List.of(mPausedMockRecording));
when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
.thenReturn(false);
when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
.thenReturn(false);
mController = new AppOpsControllerImpl( mController = new AppOpsControllerImpl(
mContext, mContext,
mTestableLooper.getLooper(), mTestableLooper.getLooper(),
mDumpManager, mDumpManager,
mFlagsCache, mFlagsCache,
mAudioManager, mAudioManager,
mSensorPrivacyController,
mDispatcher mDispatcher
); );
} }
@@ -133,6 +145,7 @@ public class AppOpsControllerTest extends SysuiTestCase {
mController.setListening(true); mController.setListening(true);
verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController); verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any()); verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
verify(mSensorPrivacyController, times(1)).addCallback(mController);
} }
@Test @Test
@@ -140,6 +153,7 @@ public class AppOpsControllerTest extends SysuiTestCase {
mController.setListening(false); mController.setListening(false);
verify(mAppOpsManager, times(1)).stopWatchingActive(mController); verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
verify(mDispatcher, times(1)).unregisterReceiver(mController); verify(mDispatcher, times(1)).unregisterReceiver(mController);
verify(mSensorPrivacyController, times(1)).removeCallback(mController);
} }
@Test @Test
@@ -476,6 +490,71 @@ public class AppOpsControllerTest extends SysuiTestCase {
AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false); 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 { private class TestHandler extends AppOpsControllerImpl.H {
TestHandler(Looper looper) { TestHandler(Looper looper) {
mController.super(looper); mController.super(looper);