summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2020-08-11 19:32:24 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-08-11 19:32:24 +0000
commit754264e60be238feb98381dc436d368d1191e63e (patch)
treed3343e3f9f0c980d0acfe6fafdfad30d2e35b097
parentad95d5b3708c7ec2e8083ed07a75fa7836badf1f (diff)
parent44fc7138bf8487908e979d25e220fd04c10aa38e (diff)
Merge "Keep track of audio recordings that are silenced"
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java107
3 files changed, 204 insertions, 13 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 7e5b42653210..93a8df41c673 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,7 +25,9 @@ public class AppOpItem {
private int mUid;
private String mPackageName;
private long mTimeStarted;
- private String mState;
+ private StringBuilder mState;
+ // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
+ private boolean mSilenced;
public AppOpItem(int code, int uid, String packageName, long timeStarted) {
this.mCode = code;
@@ -36,9 +38,8 @@ public class AppOpItem {
.append("AppOpItem(")
.append("Op code=").append(code).append(", ")
.append("UID=").append(uid).append(", ")
- .append("Package name=").append(packageName)
- .append(")")
- .toString();
+ .append("Package name=").append(packageName).append(", ")
+ .append("Paused=");
}
public int getCode() {
@@ -57,8 +58,16 @@ public class AppOpItem {
return mTimeStarted;
}
+ public void setSilenced(boolean silenced) {
+ mSilenced = silenced;
+ }
+
+ public boolean isSilenced() {
+ return mSilenced;
+ }
+
@Override
public String toString() {
- return mState;
+ return mState.append(mSilenced).append(")").toString();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 6512624f5064..8187a2235c0a 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -19,6 +19,8 @@ package com.android.systemui.appops;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -63,6 +65,7 @@ public class AppOpsControllerImpl implements AppOpsController,
private static final boolean DEBUG = false;
private final AppOpsManager mAppOps;
+ private final AudioManager mAudioManager;
private H mBGHandler;
private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
@@ -73,6 +76,9 @@ public class AppOpsControllerImpl implements AppOpsController,
private final List<AppOpItem> mActiveItems = new ArrayList<>();
@GuardedBy("mNotedItems")
private final List<AppOpItem> mNotedItems = new ArrayList<>();
+ @GuardedBy("mActiveItems")
+ private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
+ new SparseArray<>();
protected static final int[] OPS = new int[] {
AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
@@ -88,7 +94,8 @@ public class AppOpsControllerImpl implements AppOpsController,
Context context,
@Background Looper bgLooper,
DumpManager dumpManager,
- PermissionFlagsCache cache
+ PermissionFlagsCache cache,
+ AudioManager audioManager
) {
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mFlagsCache = cache;
@@ -97,6 +104,7 @@ public class AppOpsControllerImpl implements AppOpsController,
for (int i = 0; i < numOps; i++) {
mCallbacksByCode.put(OPS[i], new ArraySet<>());
}
+ mAudioManager = audioManager;
dumpManager.registerDumpable(TAG, this);
}
@@ -111,12 +119,19 @@ public class AppOpsControllerImpl implements AppOpsController,
if (listening) {
mAppOps.startWatchingActive(OPS, this);
mAppOps.startWatchingNoted(OPS, this);
+ mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+ mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
+ mAudioManager.getActiveRecordingConfigurations()));
+
} else {
mAppOps.stopWatchingActive(this);
mAppOps.stopWatchingNoted(this);
+ mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+
mBGHandler.removeCallbacksAndMessages(null); // null removes all
synchronized (mActiveItems) {
mActiveItems.clear();
+ mRecordingsByUid.clear();
}
synchronized (mNotedItems) {
mNotedItems.clear();
@@ -189,9 +204,12 @@ public class AppOpsControllerImpl implements AppOpsController,
AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
if (item == null && active) {
item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
+ if (code == AppOpsManager.OP_RECORD_AUDIO) {
+ item.setSilenced(isAnyRecordingPausedLocked(uid));
+ }
mActiveItems.add(item);
if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
- return true;
+ return !item.isSilenced();
} else if (item != null && !active) {
mActiveItems.remove(item);
if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -215,7 +233,7 @@ public class AppOpsControllerImpl implements AppOpsController,
active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
}
if (!active) {
- notifySuscribers(code, uid, packageName, false);
+ notifySuscribersWorker(code, uid, packageName, false);
}
}
@@ -324,7 +342,7 @@ public class AppOpsControllerImpl implements AppOpsController,
AppOpItem item = mActiveItems.get(i);
if ((userId == UserHandle.USER_ALL
|| UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ && isUserVisible(item) && !item.isSilenced()) {
list.add(item);
}
}
@@ -343,6 +361,10 @@ public class AppOpsControllerImpl implements AppOpsController,
return list;
}
+ private void notifySuscribers(int code, int uid, String packageName, boolean active) {
+ mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active));
+ }
+
@Override
public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
if (DEBUG) {
@@ -360,7 +382,7 @@ public class AppOpsControllerImpl implements AppOpsController,
// If active is false, we only send the update if the op is not actively noted (prevent
// early removal)
if (!alsoNoted) {
- mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
+ notifySuscribers(code, uid, packageName, active);
}
}
@@ -378,11 +400,11 @@ public class AppOpsControllerImpl implements AppOpsController,
alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
}
if (!alsoActive) {
- mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
+ notifySuscribers(code, uid, packageName, true);
}
}
- private void notifySuscribers(int code, int uid, String packageName, boolean active) {
+ private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) {
if (mCallbacksByCode.contains(code) && isUserVisible(code, uid, packageName)) {
if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
for (Callback cb: mCallbacksByCode.get(code)) {
@@ -408,6 +430,61 @@ public class AppOpsControllerImpl implements AppOpsController,
}
+ private boolean isAnyRecordingPausedLocked(int uid) {
+ List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid);
+ if (configs == null) return false;
+ int configsNum = configs.size();
+ for (int i = 0; i < configsNum; i++) {
+ AudioRecordingConfiguration config = configs.get(i);
+ if (config.isClientSilenced()) return true;
+ }
+ return false;
+ }
+
+ private void updateRecordingPausedStatus() {
+ synchronized (mActiveItems) {
+ int size = mActiveItems.size();
+ for (int i = 0; i < size; i++) {
+ AppOpItem item = mActiveItems.get(i);
+ 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()
+ );
+ }
+ }
+ }
+ }
+ }
+
+ private AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+ new AudioManager.AudioRecordingCallback() {
+ @Override
+ public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+ synchronized (mActiveItems) {
+ mRecordingsByUid.clear();
+ final int recordingsCount = configs.size();
+ for (int i = 0; i < recordingsCount; i++) {
+ AudioRecordingConfiguration recording = configs.get(i);
+
+ ArrayList<AudioRecordingConfiguration> recordings = mRecordingsByUid.get(
+ recording.getClientUid());
+ if (recordings == null) {
+ recordings = new ArrayList<>();
+ mRecordingsByUid.put(recording.getClientUid(), recordings);
+ }
+ recordings.add(recording);
+ }
+ }
+ updateRecordingPausedStatus();
+ }
+ };
+
protected class H extends Handler {
H(Looper looper) {
super(looper);
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 4fdc06e64e2c..8f082c15df36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -27,6 +27,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -34,6 +37,8 @@ import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
@@ -47,9 +52,11 @@ import com.android.systemui.dump.DumpManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Collections;
import java.util.List;
@SmallTest
@@ -73,6 +80,12 @@ public class AppOpsControllerTest extends SysuiTestCase {
private PermissionFlagsCache mFlagsCache;
@Mock
private PackageManager mPackageManager;
+ @Mock(stubOnly = true)
+ private AudioManager mAudioManager;
+ @Mock(stubOnly = true)
+ private AudioManager.AudioRecordingCallback mRecordingCallback;
+ @Mock(stubOnly = true)
+ private AudioRecordingConfiguration mPausedMockRecording;
private AppOpsControllerImpl mController;
private TestableLooper mTestableLooper;
@@ -94,11 +107,20 @@ public class AppOpsControllerTest extends SysuiTestCase {
when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
eq(TEST_UID_NON_USER_SENSITIVE))).thenReturn(0);
+ doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0))
+ .when(mAudioManager).registerAudioRecordingCallback(any(), any());
+ when(mPausedMockRecording.getClientUid()).thenReturn(TEST_UID);
+ when(mPausedMockRecording.isClientSilenced()).thenReturn(true);
+
+ when(mAudioManager.getActiveRecordingConfigurations())
+ .thenReturn(List.of(mPausedMockRecording));
+
mController = new AppOpsControllerImpl(
mContext,
mTestableLooper.getLooper(),
mDumpManager,
- mFlagsCache
+ mFlagsCache,
+ mAudioManager
);
}
@@ -363,6 +385,89 @@ public class AppOpsControllerTest extends SysuiTestCase {
AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
}
+ @Test
+ public void testPausedRecordingIsRetrievedOnCreation() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mTestableLooper.processAllMessages();
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback, never())
+ .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean());
+ }
+
+ @Test
+ public void testPausedRecordingFilteredOut() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mTestableLooper.processAllMessages();
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+
+ assertTrue(mController.getActiveAppOps().isEmpty());
+ }
+
+ @Test
+ public void testOnlyRecordAudioPaused() {
+ mController.addCallback(new int[]{
+ AppOpsManager.OP_RECORD_AUDIO,
+ AppOpsManager.OP_CAMERA
+ }, mCallback);
+ mTestableLooper.processAllMessages();
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true);
+ List<AppOpItem> list = mController.getActiveAppOps();
+
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+ }
+
+ @Test
+ public void testUnpausedRecordingSentActive() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mTestableLooper.processAllMessages();
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_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);
+ }
+
+ @Test
+ public void testAudioPausedSentInactive() {
+ mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+ mTestableLooper.processAllMessages();
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_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);
+ }
+
private class TestHandler extends AppOpsControllerImpl.H {
TestHandler(Looper looper) {
mController.super(looper);