DO NOT MERGE revoke certain app-ops on suspend

Revoking an apps authorizations to use camera and record or play audio
while suspended. Appops watchers will also be notified of this change to
re-evaluate privileges at the time of suspension.

Test: atest FrameworksServicesTests:SuspendPackagesTest
atest GtsSuspendAppsTestCases

Bug: 138636979
Change-Id: Ie95555856afdd56728125f7e60b6a78cf9fc0e58
Merged-In: Ic5fb1807deceabfd956b666fa76f8bcc94020ac3
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7569363..f0fac67 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,9 +18,11 @@
 
 import static android.app.AppOpsManager.MAX_PRIORITY_UID_STATE;
 import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE;
+import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
 import static android.app.AppOpsManager.UID_STATE_CACHED;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
@@ -173,6 +175,12 @@
         UID_STATE_CACHED,               // ActivityManager.PROCESS_STATE_NONEXISTENT
     };
 
+    private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+            OP_PLAY_AUDIO,
+            OP_RECORD_AUDIO,
+            OP_CAMERA,
+    };
+
     Context mContext;
     final AtomicFile mFile;
     final Handler mHandler;
@@ -784,20 +792,22 @@
                 final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                 final String[] changedPkgs = intent.getStringArrayExtra(
                         Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                ArraySet<ModeCallback> callbacks;
-                synchronized (AppOpsService.this) {
-                    callbacks = mOpModeWatchers.get(OP_PLAY_AUDIO);
-                    if (callbacks == null) {
-                        return;
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<ModeCallback> callbacks;
+                    synchronized (AppOpsService.this) {
+                        callbacks = mOpModeWatchers.get(code);
+                        if (callbacks == null) {
+                            continue;
+                        }
+                        callbacks = new ArraySet<>(callbacks);
                     }
-                    callbacks = new ArraySet<>(callbacks);
-                }
-                for (int i = 0; i < changedUids.length; i++) {
-                    final int changedUid = changedUids[i];
-                    final String changedPkg = changedPkgs[i];
-                    // We trust packagemanager to insert matching uid and packageNames in the
-                    // extras
-                    notifyOpChanged(callbacks, OP_PLAY_AUDIO, changedUid, changedPkg);
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
+                        notifyOpChanged(callbacks, code, changedUid, changedPkg);
+                    }
                 }
             }
         }, packageSuspendFilter);
@@ -1800,6 +1810,9 @@
      */
     private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
                 boolean raw, boolean verify) {
+        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
         synchronized (this) {
             if (verify) {
                 checkPackage(uid, packageName);
@@ -2654,6 +2667,14 @@
         return op;
     }
 
+    private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+            return false;
+        }
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+    }
+
     private boolean isOpRestrictedLocked(int uid, int code, String packageName) {
         int userHandle = UserHandle.getUserId(uid);
         final int restrictionSetCount = mOpUserRestrictions.size();
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index d5d32bd..376f5cd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -18,7 +18,10 @@
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.opToName;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,7 +42,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
 import android.content.res.Resources;
-import android.media.AudioAttributes;
 import android.os.BaseBundle;
 import android.os.Bundle;
 import android.os.Handler;
@@ -551,28 +553,42 @@
     }
 
     @Test
-    public void testAudioOpBlockedOnSuspend() throws Exception {
+    public void testCameraBlockedOnSuspend() throws Exception {
+        assertOpBlockedOnSuspend(OP_CAMERA);
+    }
+
+    @Test
+    public void testPlayAudioBlockedOnSuspend() throws Exception {
+        assertOpBlockedOnSuspend(OP_PLAY_AUDIO);
+    }
+
+    @Test
+    public void testRecordAudioBlockedOnSuspend() throws Exception {
+        assertOpBlockedOnSuspend(OP_RECORD_AUDIO);
+    }
+
+    private void assertOpBlockedOnSuspend(int code) throws Exception {
         final IAppOpsService iAppOps = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
         final CountDownLatch latch = new CountDownLatch(1);
         final IAppOpsCallback watcher = new IAppOpsCallback.Stub() {
             @Override
             public void opChanged(int op, int uid, String packageName) {
-                if (op == OP_PLAY_AUDIO && packageName.equals(TEST_APP_PACKAGE_NAME)) {
+                if (op == code && packageName.equals(TEST_APP_PACKAGE_NAME)) {
                     latch.countDown();
                 }
             }
         };
-        iAppOps.startWatchingMode(OP_PLAY_AUDIO, TEST_APP_PACKAGE_NAME, watcher);
+        iAppOps.startWatchingMode(code, TEST_APP_PACKAGE_NAME, watcher);
         final int testPackageUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0);
-        int audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO,
-                AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME);
-        assertEquals("Audio muted for unsuspended package", MODE_ALLOWED, audioOpMode);
+        int opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME);
+        assertEquals("Op " + opToName(code) + " disallowed for unsuspended package", MODE_ALLOWED,
+                opMode);
         suspendTestPackage(null, null, null);
         assertTrue("AppOpsWatcher did not callback", latch.await(5, TimeUnit.SECONDS));
-        audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO,
-                AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME);
-        assertEquals("Audio not muted for suspended package", MODE_IGNORED, audioOpMode);
+        opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME);
+        assertEquals("Op " + opToName(code) + " allowed for suspended package", MODE_IGNORED,
+                opMode);
         iAppOps.stopWatchingMode(watcher);
     }