Merge "Hide indicators when camera/mic disabled" into sc-dev
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user