Wire call to suspend a package

Adds APIs in DevicePolicyManager and PackageManager for allowing
a device admin to suspend a package. PackageManagerService sets
or unsets a new PackageUserState 'suspended' setting. Terminal
command to suspend/unsuspend has been added via
PackageManagerShellCommand (as root).

Next steps:
* use the new 'suspended' setting for denying access to start app
(probably in ActivityStackSupervisor)
* broadcast a PACKAGE_(UN)SUSPENDED intent for launchers to pick up
* remove app from recents (go further and kill it if it is running)
* erase existing notifications for this app

Bug: 22776576
Change-Id: I718b3498f6a53cc0c6fdfb6d15031e53ddca4353
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 460e68c..77721e6 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1858,6 +1858,16 @@
     }
 
     @Override
+    public boolean setPackageSuspendedAsUser(String packageName, boolean suspended, int userId) {
+        try {
+            return mPM.setPackageSuspendedAsUser(packageName, suspended, userId);
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
+        return false;
+    }
+
+    @Override
     public void getPackageSizeInfo(String packageName, int userHandle,
             IPackageStatsObserver observer) {
         try {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 08b7d62..660ce3b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3092,6 +3092,48 @@
     }
 
     /**
+     * Called by device or profile owners for setting the package suspended for this user.
+     * A suspended package will not be started by the package manager, its notifications will
+     * be hidden and it will not show up in recents. The package must already be installed.
+     *
+     * @param admin The name of the admin component to check.
+     * @param packageName The package name of the app to suspend or unsuspend.
+     * @param suspended If set to {@code true} than the package will be suspended, if set to
+     * {@code false} the package will be unsuspended.
+     * @return boolean {@code true} if the operation was successfully performed, {@code false}
+     * otherwise.
+     */
+    public boolean setPackageSuspended(@NonNull ComponentName admin, String packageName,
+            boolean suspended) {
+        if (mService != null) {
+            try {
+                return mService.setPackageSuspended(admin, packageName, suspended);
+            } catch (RemoteException re) {
+                Log.w(TAG, "Failed talking with device policy service", re);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by device or profile owners to determine if a package is suspended.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param packageName The name of the package to retrieve the suspended status of.
+     * @return boolean {@code true} if the package is suspended, {@code false} otherwise.
+     */
+    public boolean getPackageSuspended(@NonNull ComponentName admin, String packageName) {
+        if (mService != null) {
+            try {
+                return mService.getPackageSuspended(admin, packageName);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return false;
+    }
+
+    /**
      * Sets the enabled state of the profile. A profile should be enabled only once it is ready to
      * be used. Only the profile owner can call this.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1708ee3..d8cc2ea 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -130,6 +130,9 @@
     boolean setDeviceOwnerLockScreenInfo(in ComponentName who, String deviceOwnerInfo);
     String getDeviceOwnerLockScreenInfo();
 
+    boolean setPackageSuspended(in ComponentName admin, String packageName, boolean suspended);
+    boolean getPackageSuspended(in ComponentName admin, String packageName);
+
     boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
     void uninstallCaCerts(in ComponentName admin, in String[] aliases);
     void enforceCanManageCaCerts(in ComponentName admin);
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1eeace1..807c0a2 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -382,6 +382,12 @@
     public static final int FLAG_HARDWARE_ACCELERATED = 1<<29;
 
     /**
+     * Value for {@link #flags}: true if this application's package is in
+     * the suspended state.
+     */
+    public static final int FLAG_SUSPENDED = 1<<30;
+
+    /**
      * Value for {@link #flags}: true if code from this application will need to be
      * loaded into other applications' processes. On devices that support multiple
      * instruction sets, this implies the code might be loaded into a process that's
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b947a2b..4d0d146 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -285,6 +285,8 @@
 
     void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
 
+    boolean setPackageSuspendedAsUser(String packageName, boolean suspended, int userId);
+
     /**
      * Backup/restore support - only the system uid may use these.
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c40c8a6..3e7deb9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4684,6 +4684,21 @@
         throw new UnsupportedOperationException();
     }
 
+    /**
+     * Puts the package in a suspended state, making the package un-runnable,
+     * but it doesn't remove the data or the actual package file. The application notifications
+     * will be hidden and also the application will not show up in recents.
+     *
+     * @param packageName The name of the package to set the suspended status.
+     * @param suspended If set to {@code true} than the package will be suspended, if set to
+     * {@code false} the package will be unsuspended.
+     * @param userId The user id.
+     *
+     * @hide
+     */
+    public abstract boolean setPackageSuspendedAsUser(
+            String packageName, boolean suspended, int userId);
+
     /** {@hide} */
     public static boolean isMoveStatusFinished(int status) {
         return (status < 0 || status > 100);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b79b6b6..019ed2b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4883,6 +4883,11 @@
         } else {
             ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
         }
+        if (state.suspended) {
+            ai.flags |= ApplicationInfo.FLAG_SUSPENDED;
+        } else {
+            ai.flags &= ~ApplicationInfo.FLAG_SUSPENDED;
+        }
         if (state.hidden) {
             ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
         } else {
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 9b28401..91fdf7f 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -29,6 +29,7 @@
     public boolean notLaunched;
     public boolean installed;
     public boolean hidden; // Is the app restricted by owner / admin
+    public boolean suspended;
     public int enabled;
     public boolean blockUninstall;
 
@@ -43,6 +44,7 @@
     public PackageUserState() {
         installed = true;
         hidden = false;
+        suspended = false;
         enabled = COMPONENT_ENABLED_STATE_DEFAULT;
         domainVerificationStatus =
                 PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
@@ -54,6 +56,7 @@
         notLaunched = o.notLaunched;
         enabled = o.enabled;
         hidden = o.hidden;
+        suspended = o.suspended;
         lastDisableAppCaller = o.lastDisableAppCaller;
         disabledComponents = o.disabledComponents != null
                 ? new ArraySet<>(o.disabledComponents) : null;