Merge "Hide indicators when camera/mic disabled" into sc-dev

This commit is contained in:
Nate Myren
2021-01-29 17:22:48 +00:00
committed by Android (Google) Code Review
4 changed files with 145 additions and 30 deletions

View File

@@ -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;
@@ -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();
}
}

View File

@@ -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;
@@ -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;
@@ -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
@@ -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;
@@ -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<>();
@@ -118,6 +123,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
DumpManager dumpManager,
PermissionFlagsCache cache,
AudioManager audioManager,
IndividualSensorPrivacyController sensorPrivacyController,
BroadcastDispatcher dispatcher
) {
mDispatcher = dispatcher;
@@ -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);
@@ -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,
@@ -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);
@@ -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());
@@ -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);
}
}
@@ -512,22 +530,27 @@ 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);
notifySuscribers(
item.getCode(),
item.getUid(),
item.getPackageName(),
!item.isSilenced()
);
}
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.isDisabled()
);
}
}
}
@@ -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 {

View File

@@ -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;
@@ -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();

View File

@@ -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;
@@ -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;
@@ -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()
@@ -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
);
}
@@ -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
@@ -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
@@ -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);