Allow callers with QUARANTINE_APPS permission to quarantine apps.

Bug: 301109246
Test: atest tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
Change-Id: I4e33009a2328554c093d3939fc9a183aaa75d68c
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 346d62b..e24b290 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3937,7 +3937,7 @@
     method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String);
     method @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo);
-    method @FlaggedApi("android.content.pm.quarantined_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
+    method @FlaggedApi("android.content.pm.quarantined_enabled") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.QUARANTINE_APPS}, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
     method public void setSystemAppState(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e2a5747..0aadd60 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -9781,7 +9781,8 @@
      * launcher to support customization that they might need to handle the suspended state.
      *
      * <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} to use this API except for
-     * device owner and profile owner.
+     * device owner and profile owner or the {@link Manifest.permission#QUARANTINE_APPS} if the
+     * caller is using {@link #FLAG_SUSPEND_QUARANTINED}.
      *
      * @param packageNames The names of the packages to set the suspended status.
      * @param suspended If set to {@code true}, the packages will be suspended, if set to
@@ -9809,7 +9810,10 @@
      */
     @SystemApi
     @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
-    @RequiresPermission(value=Manifest.permission.SUSPEND_APPS, conditional=true)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.SUSPEND_APPS,
+            Manifest.permission.QUARANTINE_APPS
+    }, conditional = true)
     @SuppressLint("NullableCollection")
     @Nullable
     public String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ec302e7..e5dac6f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2365,7 +2365,7 @@
          them from running without explicit user action.
     -->
     <permission android:name="android.permission.QUARANTINE_APPS"
-        android:protectionLevel="internal|verifier" />
+        android:protectionLevel="signature|verifier" />
 
     <!-- Allows applications to discover and pair bluetooth devices.
          <p>Protection level: normal
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 10d04d3..ab11242 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -325,6 +325,7 @@
     <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
     <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
     <uses-permission android:name="android.permission.SUSPEND_APPS" />
+    <uses-permission android:name="android.permission.QUARANTINE_APPS" />
     <uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
     <!-- Permission needed to wipe the device for Test Harness Mode -->
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e5f7962..fd09fea 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3105,7 +3105,8 @@
     }
 
     private void enforceCanSetPackagesSuspendedAsUser(@NonNull Computer snapshot,
-            String callingPackage, int callingUid, int userId, String callingMethod) {
+            boolean quarantined, String callingPackage, int callingUid, int userId,
+            String callingMethod) {
         if (callingUid == Process.ROOT_UID
                 // Need to compare app-id to allow system dialogs access on secondary users
                 || UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
@@ -3120,8 +3121,20 @@
             }
         }
 
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
-                callingMethod);
+        if (quarantined) {
+            final boolean hasQuarantineAppsPerm = mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.QUARANTINE_APPS) == PERMISSION_GRANTED;
+            // TODO: b/305256093 - In order to facilitate testing, temporarily allowing apps
+            // with SUSPEND_APPS permission to quarantine apps. Remove this once the testing
+            // is done and this is no longer needed.
+            if (!hasQuarantineAppsPerm) {
+                mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
+                        callingMethod);
+            }
+        } else {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
+                    callingMethod);
+        }
 
         final int packageUid = snapshot.getPackageUid(callingPackage, 0, userId);
         final boolean allowedPackageUid = packageUid == callingUid;
@@ -6136,9 +6149,6 @@
                 PersistableBundle appExtras, PersistableBundle launcherExtras,
                 SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId) {
             final int callingUid = Binder.getCallingUid();
-            final Computer snapshot = snapshotComputer();
-            enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,
-                    "setPackagesSuspendedAsUser");
             boolean quarantined = false;
             if (Flags.quarantinedEnabled()) {
                 if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
@@ -6149,6 +6159,9 @@
                     quarantined = callingPackage.equals(wellbeingPkg);
                 }
             }
+            final Computer snapshot = snapshotComputer();
+            enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, callingPackage, callingUid,
+                    userId, "setPackagesSuspendedAsUser");
             return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
                     appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
                     quarantined);