Allow Shell to change component enabled state

But make sure that we don't allow Shell or other apps
to disable an active profile or device owner.

Also limit exactly what states Shell can switch apps
between, similar to Settings UI.

This is required for some CTS tests

Bug: 27924655
Change-Id: I958f0d1de7f0bc1f5a0cbf853d57dfdeb2f9ad59
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4e9b59f..9c0aa35d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -111,6 +111,7 @@
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <!-- Permission needed to rename bugreport notifications (so they're not shown as Shell) -->
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b6c269e..c061720 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17484,6 +17484,14 @@
                 throw new IllegalArgumentException(
                         "Unknown component: " + packageName + "/" + className);
             }
+            // Don't allow other apps to disable an active profile owner
+            if (!UserHandle.isSameApp(uid, pkgSetting.appId)) {
+                final DevicePolicyManagerInternal dpmi = LocalServices
+                        .getService(DevicePolicyManagerInternal.class);
+                if (dpmi != null && dpmi.hasDeviceOwnerOrProfileOwner(packageName, userId)) {
+                    throw new SecurityException("Cannot disable a device owner or a profile owner");
+                }
+            }
             // Allow root and verify that userId is not being specified by a different user
             if (!allowedByPermission && !UserHandle.isSameApp(uid, pkgSetting.appId)) {
                 throw new SecurityException(
@@ -17491,6 +17499,25 @@
                         + Binder.getCallingPid()
                         + ", uid=" + uid + ", package uid=" + pkgSetting.appId);
             }
+            if (uid == Process.SHELL_UID) {
+                // Shell can only change whole packages between ENABLED and DISABLED_USER states
+                int oldState = pkgSetting.getEnabled(userId);
+                if (className == null
+                    &&
+                    (oldState == COMPONENT_ENABLED_STATE_DISABLED_USER
+                     || oldState == COMPONENT_ENABLED_STATE_DEFAULT
+                     || oldState == COMPONENT_ENABLED_STATE_ENABLED)
+                    &&
+                    (newState == COMPONENT_ENABLED_STATE_DISABLED_USER
+                     || newState == COMPONENT_ENABLED_STATE_DEFAULT
+                     || newState == COMPONENT_ENABLED_STATE_ENABLED)) {
+                    // ok
+                } else {
+                    throw new SecurityException(
+                            "Shell cannot change component state for " + packageName + "/"
+                            + className + " to " + newState);
+                }
+            }
             if (className == null) {
                 // We're dealing with an application/package level state change
                 if (pkgSetting.getEnabled(userId) == newState) {