Always check device policy for device aware permission check
and AppOp mode checking

Before, when the deviceId in the Context is explicitly set, we don't
account for virtual device's camera/audio policy when doing permission
check. So if the virtual device doesn't have custom policy, the
permission check will not fall back to the default device.

With this fix, we always fall back to the default device if virtual
device uses the default policy, no matter if the device Id is explicitly
set in the Context or not.

Bug: 396422648
Test: atest android.permission.cts.DevicePermissionsTest
Flag: EXEMPT bug fix
Change-Id: I5bc731c275429bd6ed8d7117d2226bd41f4b7c34
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1a6e9b0..dc5974f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -22,14 +22,12 @@
 import static android.permission.flags.Flags.shouldRegisterAttributionSource;
 import static android.view.WindowManager.LayoutParams.WindowType;
 
-import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UiContext;
-import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
@@ -2367,47 +2365,11 @@
             Log.v(TAG, "Treating renounced permission " + permission + " as denied");
             return PERMISSION_DENIED;
         }
-        int deviceId = resolveDeviceIdForPermissionCheck(permission);
+        int deviceId = PermissionManager.resolveDeviceIdForPermissionCheck(this, getDeviceId(),
+                permission);
         return PermissionManager.checkPermission(permission, pid, uid, deviceId);
     }
 
-    private int resolveDeviceIdForPermissionCheck(String permission) {
-        // When checking a device-aware permission on a remote device, if the permission is CAMERA
-        // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
-        // device doesn't have capability fall back to checking permission on the default device.
-        // Note: we only perform permission check redirection when the device id is not explicitly
-        // set in the context.
-        int deviceId = getDeviceId();
-        if (deviceId != Context.DEVICE_ID_DEFAULT
-                && !mIsExplicitDeviceId
-                && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) {
-            VirtualDeviceManager virtualDeviceManager =
-                    getSystemService(VirtualDeviceManager.class);
-            if (virtualDeviceManager == null) {
-                Slog.e(
-                        TAG,
-                        "VDM is not enabled when device id is not default. deviceId = "
-                                + deviceId);
-            } else {
-                VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
-                if (virtualDevice != null) {
-                    if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
-                            && !virtualDevice.hasCustomAudioInputSupport())
-                            || (Objects.equals(permission, Manifest.permission.CAMERA)
-                            && !virtualDevice.hasCustomCameraSupport())) {
-                        deviceId = Context.DEVICE_ID_DEFAULT;
-                    }
-                } else {
-                    Slog.e(
-                            TAG,
-                            "virtualDevice is not found when device id is not default. deviceId = "
-                                    + deviceId);
-                }
-            }
-        }
-        return deviceId;
-    }
-
     /** @hide */
     @Override
     public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
@@ -2511,7 +2473,8 @@
     @Override
     public int getPermissionRequestState(String permission) {
         Objects.requireNonNull(permission, "Permission name can't be null");
-        int deviceId = resolveDeviceIdForPermissionCheck(permission);
+        int deviceId = PermissionManager.resolveDeviceIdForPermissionCheck(this, getDeviceId(),
+                permission);
         PermissionManager permissionManager = getSystemService(PermissionManager.class);
         return permissionManager.getPermissionRequestState(getOpPackageName(), permission,
                 deviceId);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 5188204..561a2c9 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -16,6 +16,7 @@
 
 package android.permission;
 
+import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -2039,12 +2040,49 @@
                 new PackageNamePermissionQuery(permName, pkgName, persistentDeviceId, userId));
     }
 
+    /**
+     * When checking a device-aware permission on a remote device, if the permission is CAMERA
+     * or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
+     * device doesn't have capability fall back to checking permission on the default device.
+     *
+     * @hide
+     */
+    public static int resolveDeviceIdForPermissionCheck(@NonNull Context context, int deviceId,
+            @Nullable String permission) {
+        if (deviceId == Context.DEVICE_ID_DEFAULT || !DEVICE_AWARE_PERMISSIONS.contains(
+                permission)) {
+            return Context.DEVICE_ID_DEFAULT;
+        }
+
+        VirtualDeviceManager virtualDeviceManager =
+                context.getSystemService(VirtualDeviceManager.class);
+        if (virtualDeviceManager == null) {
+            Slog.e(LOG_TAG, "VDM is not enabled when device id is not default. deviceId = "
+                    + deviceId);
+        } else {
+            VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+            if (virtualDevice != null) {
+                if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
+                        && !virtualDevice.hasCustomAudioInputSupport())
+                        || (Objects.equals(permission, Manifest.permission.CAMERA)
+                        && !virtualDevice.hasCustomCameraSupport())) {
+                    deviceId = Context.DEVICE_ID_DEFAULT;
+                }
+            } else {
+                Slog.e(LOG_TAG,
+                        "virtualDevice is not found when device id is not default. deviceId = "
+                                + deviceId);
+            }
+        }
+        return deviceId;
+    }
+
     @Nullable
     private String getPersistentDeviceId(int deviceId) {
         String persistentDeviceId = null;
 
         if (deviceId == Context.DEVICE_ID_DEFAULT) {
-            persistentDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+            persistentDeviceId = PERSISTENT_DEVICE_ID_DEFAULT;
         } else {
             VirtualDeviceManager virtualDeviceManager = mContext.getSystemService(
                     VirtualDeviceManager.class);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 2a9762c..b7710f9 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2996,7 +2996,7 @@
             UidState uidState = getUidStateLocked(uid, false);
             if (uidState != null) {
                 int rawUidMode = mAppOpsCheckingService.getUidMode(
-                        uidState.uid, getPersistentId(virtualDeviceId), code);
+                        uidState.uid, getPersistentDeviceIdForOp(virtualDeviceId, code), code);
 
                 if (rawUidMode != AppOpsManager.opToDefaultMode(code)) {
                     return raw ? rawUidMode :
@@ -3059,7 +3059,7 @@
 
             int switchCode = AppOpsManager.opToSwitch(code);
             int rawUidMode = mAppOpsCheckingService.getUidMode(uid,
-                    getPersistentId(virtualDeviceId), switchCode);
+                    getPersistentDeviceIdForOp(virtualDeviceId, switchCode), switchCode);
 
             if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) {
                 return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode);
@@ -3386,7 +3386,7 @@
             }
             final Op op = getOpLocked(ops, code, uid, true);
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
-                    getPersistentId(virtualDeviceId));
+                    getPersistentDeviceIdForOp(virtualDeviceId, code));
             if (attributedOp.isRunning()) {
                 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
                         + code + " startTime of in progress event="
@@ -3408,15 +3408,15 @@
 
                 // If there is a non-default per UID policy (we set UID op mode only if
                 // non-default) it takes over, otherwise use the per package policy.
-            } else if (mAppOpsCheckingService.getUidMode(
-                            uidState.uid, getPersistentId(virtualDeviceId), switchCode)
+            } else if (mAppOpsCheckingService.getUidMode(uidState.uid,
+                    getPersistentDeviceIdForOp(virtualDeviceId, switchCode), switchCode)
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode =
                         uidState.evalMode(
                                 code,
                                 mAppOpsCheckingService.getUidMode(
                                         uidState.uid,
-                                        getPersistentId(virtualDeviceId),
+                                        getPersistentDeviceIdForOp(virtualDeviceId, switchCode),
                                         switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
@@ -3468,7 +3468,8 @@
                     virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
 
             attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags, notedCount);
+                    getPersistentDeviceIdForOp(proxyVirtualDeviceId, code), uidState.getState(),
+                    flags, notedCount);
 
             if (shouldCollectAsyncNotedOp) {
                 collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -4035,7 +4036,7 @@
             }
             final Op op = getOpLocked(ops, code, uid, true);
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
-                    getPersistentId(virtualDeviceId));
+                    getPersistentDeviceIdForOp(virtualDeviceId, code));
             final UidState uidState = ops.uidState;
             isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
                     virtualDeviceId, pvr.bypass, false);
@@ -4048,8 +4049,9 @@
                 // If there is a non-default per UID policy (we set UID op mode only if
                 // non-default) it takes over, otherwise use the per package policy.
             } else if ((rawUidMode =
-                            mAppOpsCheckingService.getUidMode(
-                                    uidState.uid, getPersistentId(virtualDeviceId), switchCode))
+                    mAppOpsCheckingService.getUidMode(
+                            uidState.uid, getPersistentDeviceIdForOp(virtualDeviceId, switchCode),
+                            switchCode))
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode = uidState.evalMode(code, rawUidMode);
                 if (!shouldStartForMode(uidMode, startIfModeDefault)) {
@@ -4097,11 +4099,13 @@
             try {
                 if (isRestricted) {
                     attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName,
-                            proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+                            proxyAttributionTag,
+                            getPersistentDeviceIdForOp(proxyVirtualDeviceId, code),
                             uidState.getState(), flags, attributionFlags, attributionChainId);
                 } else {
                     attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName,
-                            proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+                            proxyAttributionTag,
+                            getPersistentDeviceIdForOp(proxyVirtualDeviceId, code),
                             uidState.getState(), flags, attributionFlags, attributionChainId);
                     startType = START_TYPE_STARTED;
                 }
@@ -4169,15 +4173,15 @@
             final int switchCode = AppOpsManager.opToSwitch(code);
             // If there is a non-default mode per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
-            if (mAppOpsCheckingService.getUidMode(
-                            uidState.uid, getPersistentId(virtualDeviceId), switchCode)
+            if (mAppOpsCheckingService.getUidMode(uidState.uid,
+                    getPersistentDeviceIdForOp(virtualDeviceId, switchCode), switchCode)
                     != AppOpsManager.opToDefaultMode(switchCode)) {
                 final int uidMode =
                         uidState.evalMode(
                                 code,
                                 mAppOpsCheckingService.getUidMode(
                                         uidState.uid,
-                                        getPersistentId(virtualDeviceId),
+                                        getPersistentDeviceIdForOp(virtualDeviceId, switchCode),
                                         switchCode));
                 if (!shouldStartForMode(uidMode, startIfModeDefault)) {
                     if (DEBUG) {
@@ -4340,7 +4344,8 @@
                 return;
             }
             final AttributedOp attributedOp =
-                    op.mDeviceAttributedOps.getOrDefault(getPersistentId(virtualDeviceId),
+                    op.mDeviceAttributedOps.getOrDefault(
+                            getPersistentDeviceIdForOp(virtualDeviceId, code),
                             new ArrayMap<>()).get(attributionTag);
             if (attributedOp == null) {
                 Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
@@ -4631,7 +4636,8 @@
             return true;
         }
         if (mVirtualDeviceManagerInternal == null) {
-            return true;
+            Slog.w(TAG, "VirtualDeviceManagerInternal is null when device Id is non-default");
+            return false;
         }
         if (mVirtualDeviceManagerInternal.isValidVirtualDeviceId(virtualDeviceId)) {
             mKnownDeviceIds.put(virtualDeviceId,
@@ -7299,7 +7305,13 @@
         return packageNames;
     }
 
-    @NonNull private String getPersistentId(int virtualDeviceId) {
+    // For ops associated with device aware permissions, if virtual device id is non-default, we
+    // will return string version of that id provided the virtual device has corresponding camera or
+    // audio policy.
+    @NonNull private String getPersistentDeviceIdForOp(int virtualDeviceId, int op) {
+        virtualDeviceId = PermissionManager.resolveDeviceIdForPermissionCheck(mContext,
+                virtualDeviceId, AppOpsManager.opToPermission(op));
+
         if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
             return PERSISTENT_DEVICE_ID_DEFAULT;
         }
@@ -7308,6 +7320,7 @@
         }
         String persistentId =
                 mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId);
+
         if (persistentId == null) {
             persistentId = mKnownDeviceIds.get(virtualDeviceId);
         }