Add new disabled state for "optional" built-in apps.

The disabled state allows you to make an app disabled
except for whatever parts of the system still want to
provide access to them and automatically enable them
if the user want to use it.

Currently the input method manager service is the only
part of the system that supports this, so you can put
an IME in this state and it will generally look disabled
but still be available in the IME list and once selected
switched to the enabled state.

Change-Id: I77f01c70610d82ce9070d4aabbadec8ae2cff2a3
diff --git a/api/current.txt b/api/current.txt
index d367a00..1995416 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6627,6 +6627,7 @@
     method public abstract void verifyPendingInstall(int, int);
     field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0
     field public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; // 0x2
+    field public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4; // 0x4
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
     field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
     field public static final int DONT_KILL_APP = 1; // 0x1
@@ -6673,6 +6674,7 @@
     field public static final int GET_ACTIVITIES = 1; // 0x1
     field public static final int GET_CONFIGURATIONS = 16384; // 0x4000
     field public static final int GET_DISABLED_COMPONENTS = 512; // 0x200
+    field public static final int GET_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
     field public static final int GET_GIDS = 256; // 0x100
     field public static final int GET_INSTRUMENTATION = 16; // 0x10
     field public static final int GET_INTENT_FILTERS = 32; // 0x20
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 39539b4..f0e3370 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -135,6 +135,11 @@
             return;
         }
 
+        if ("disable-until-used".equals(op)) {
+            runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+            return;
+        }
+
         if ("grant".equals(op)) {
             runGrantRevokePermission(true);
             return;
@@ -1178,6 +1183,8 @@
                 return "disabled";
             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
                 return "disabled-user";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                return "disabled-until-used";
         }
         return "unknown";
     }
@@ -1459,6 +1466,7 @@
         System.err.println("       pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
+        System.err.println("       pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm grant PACKAGE PERMISSION");
         System.err.println("       pm revoke PACKAGE PERMISSION");
         System.err.println("       pm set-install-location [0/auto] [1/internal] [2/external]");
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 89b1bbd..b5349fd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1588,7 +1588,7 @@
      * <ul>
      * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
      * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST} containing the class name
-     * of the changed components.
+     * of the changed components (or the package name itself).
      * <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the
      * default action of restarting the application.
      * </ul>
@@ -2969,7 +2969,9 @@
 
     /**
      * This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED},
-     * and contains a string array of all of the components that have changed.
+     * and contains a string array of all of the components that have changed.  If
+     * the state of the overall package has changed, then it will contain an entry
+     * with the package name itself.
      */
     public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST =
             "android.intent.extra.changed_component_name_list";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a69f220..d80598c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -175,6 +175,14 @@
     public static final int GET_CONFIGURATIONS = 0x00004000;
 
     /**
+     * {@link PackageInfo} flag: include disabled components which are in
+     * that state only because of {@link #COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED}
+     * in the returned info.  Note that if you set this flag, applications
+     * that are in this disabled state will be reported as enabled.
+     */
+    public static final int GET_DISABLED_UNTIL_USED_COMPONENTS = 0x00008000;
+
+    /**
      * Resolution and querying flag: if set, only filters that support the
      * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for
      * matching.  This is a synonym for including the CATEGORY_DEFAULT in your
@@ -265,6 +273,19 @@
     public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3;
 
     /**
+     * Flag for {@link #setApplicationEnabledSetting(String, int, int)} only: This
+     * application should be considered, until the point where the user actually
+     * wants to use it.  This means that it will not normally show up to the user
+     * (such as in the launcher), but various parts of the user interface can
+     * use {@link #GET_DISABLED_UNTIL_USED_COMPONENTS} to still see it and allow
+     * the user to select it (as for example an IME, device admin, etc).  Such code,
+     * once the user has selected the app, should at that point also make it enabled.
+     * This option currently <strong>can not</strong> be used with
+     * {@link #setComponentEnabledSetting(ComponentName, int, int)}.
+     */
+    public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
+
+    /**
      * Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to
      * indicate that this package should be installed as forward locked, i.e. only the app itself
      * should have access to its code and non-resource assets.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3e8c2a8..e1887bc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3527,29 +3527,45 @@
         return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
     }
 
+    private static void updateApplicationInfo(ApplicationInfo ai, int flags,
+            PackageUserState state) {
+        // CompatibilityMode is global state.
+        if (!sCompatibilityModeEnabled) {
+            ai.disableCompatibilityMode();
+        }
+        if (state.installed) {
+            ai.flags |= ApplicationInfo.FLAG_INSTALLED;
+        } else {
+            ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+        }
+        if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+            ai.enabled = true;
+        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+            ai.enabled = (flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0;
+        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+                || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+            ai.enabled = false;
+        }
+        ai.enabledSetting = state.enabled;
+    }
+
     public static ApplicationInfo generateApplicationInfo(Package p, int flags,
             PackageUserState state, int userId) {
         if (p == null) return null;
         if (!checkUseInstalled(flags, state)) {
             return null;
         }
-        if (!copyNeeded(flags, p, state, null, userId)) {
-            // CompatibilityMode is global state. It's safe to modify the instance
-            // of the package.
-            if (!sCompatibilityModeEnabled) {
-                p.applicationInfo.disableCompatibilityMode();
-            }
-            // Make sure we report as installed.  Also safe to do, since the
-            // default state should be installed (we will always copy if we
-            // need to report it is not installed).
-            p.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
-            if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
-                p.applicationInfo.enabled = true;
-            } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
-                    || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
-                p.applicationInfo.enabled = false;
-            }
-            p.applicationInfo.enabledSetting = state.enabled;
+        if (!copyNeeded(flags, p, state, null, userId)
+                && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 0
+                        || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
+            // In this case it is safe to directly modify the internal ApplicationInfo state:
+            // - CompatibilityMode is global state, so will be the same for every call.
+            // - We only come in to here if the app should reported as installed; this is the
+            // default state, and we will do a copy otherwise.
+            // - The enable state will always be reported the same for the application across
+            // calls; the only exception is for the UNTIL_USED mode, and in that case we will
+            // be doing a copy.
+            updateApplicationInfo(p.applicationInfo, flags, state);
             return p.applicationInfo;
         }
 
@@ -3565,26 +3581,12 @@
         if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
             ai.sharedLibraryFiles = p.usesLibraryFiles;
         }
-        if (!sCompatibilityModeEnabled) {
-            ai.disableCompatibilityMode();
-        }
         if (state.stopped) {
             ai.flags |= ApplicationInfo.FLAG_STOPPED;
         } else {
             ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
         }
-        if (state.installed) {
-            ai.flags |= ApplicationInfo.FLAG_INSTALLED;
-        } else {
-            ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
-        }
-        if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
-            ai.enabled = true;
-        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
-                || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
-            ai.enabled = false;
-        }
-        ai.enabledSetting = state.enabled;
+        updateApplicationInfo(ai, flags, state);
         return ai;
     }
 
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 20ecace..424c19b 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -153,8 +153,33 @@
 
     public void onPackageUpdateFinished(String packageName, int uid) {
     }
-    
-    public void onPackageChanged(String packageName, int uid, String[] components) {
+
+    /**
+     * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED
+     * Intent.ACTION_PACKAGE_CHANGED} being received, informing you of
+     * changes to the enabled/disabled state of components in a package
+     * and/or of the overall package.
+     *
+     * @param packageName The name of the package that is changing.
+     * @param uid The user ID the package runs under.
+     * @param components Any components in the package that are changing.  If
+     * the overall package is changing, this will contain an entry of the
+     * package name itself.
+     * @return Return true to indicate you care about this change, which will
+     * result in {@link #onSomePackagesChanged()} being called later.  If you
+     * return false, no further callbacks will happen about this change.  The
+     * default implementation returns true if this is a change to the entire
+     * package.
+     */
+    public boolean onPackageChanged(String packageName, int uid, String[] components) {
+        if (components != null) {
+            for (String name : components) {
+                if (packageName.equals(name)) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
     
     public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
@@ -189,7 +214,10 @@
      */
     public void onPackageAppeared(String packageName, int reason) {
     }
-    
+
+    /**
+     * Called when an existing package is updated or its disabled state changes.
+     */
     public void onPackageModified(String packageName) {
     }
     
@@ -328,9 +356,10 @@
             if (pkg != null) {
                 mModifiedPackages = mTempArray;
                 mTempArray[0] = pkg;
-                onPackageChanged(pkg, uid, components);
-                // XXX Don't want this to always cause mSomePackagesChanged,
-                // since it can happen a fair amount.
+                mChangeType = PACKAGE_PERMANENT_CHANGE;
+                if (onPackageChanged(pkg, uid, components)) {
+                    mSomePackagesChanged = true;
+                }
                 onPackageModified(pkg);
             }
         } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 4d41e42..3d7e1ff 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -536,7 +536,7 @@
             }
         }
 
-        private String getEnabledInputMethodsStr() {
+        public String getEnabledInputMethodsStr() {
             mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
                     mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
             if (DEBUG) {
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 593b9bf..0f14265 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -382,6 +382,8 @@
     private boolean mInputBoundToKeyguard;
 
     class SettingsObserver extends ContentObserver {
+        String mLastEnabled = "";
+
         SettingsObserver(Handler handler) {
             super(handler);
             ContentResolver resolver = mContext.getContentResolver();
@@ -395,7 +397,13 @@
 
         @Override public void onChange(boolean selfChange) {
             synchronized (mMethodMap) {
-                updateFromSettingsLocked();
+                boolean enabledChanged = false;
+                String newEnabled = mSettings.getEnabledInputMethodsStr();
+                if (!mLastEnabled.equals(newEnabled)) {
+                    mLastEnabled = newEnabled;
+                    enabledChanged = true;
+                }
+                updateFromSettingsLocked(enabledChanged);
             }
         }
     }
@@ -539,7 +547,7 @@
                 }
 
                 if (changed) {
-                    updateFromSettingsLocked();
+                    updateFromSettingsLocked(false);
                 }
             }
         }
@@ -674,7 +682,7 @@
         }
 
         mSettingsObserver = new SettingsObserver(mHandler);
-        updateFromSettingsLocked();
+        updateFromSettingsLocked(true);
 
         // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
         // according to the new system locale.
@@ -748,7 +756,7 @@
                 // If the locale is changed, needs to reset the default ime
                 resetDefaultImeLocked(mContext);
             }
-            updateFromSettingsLocked();
+            updateFromSettingsLocked(true);
             mLastSystemLocale = newLocale;
             if (!updateOnlyWhenLocaleChanged) {
                 try {
@@ -1533,7 +1541,27 @@
         return false;
     }
 
-    void updateFromSettingsLocked() {
+    void updateFromSettingsLocked(boolean enabledMayChange) {
+        if (enabledMayChange) {
+            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+            for (int i=0; i<enabled.size(); i++) {
+                // We allow the user to select "disabled until used" apps, so if they
+                // are enabling one of those here we now need to make it enabled.
+                InputMethodInfo imm = enabled.get(i);
+                try {
+                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
+                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+                            mSettings.getCurrentUserId());
+                    if (ai.enabledSetting
+                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                        mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
+                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                                PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId());
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+        }
         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -2383,7 +2411,8 @@
 
         final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                 new Intent(InputMethod.SERVICE_INTERFACE),
-                PackageManager.GET_META_DATA, mSettings.getCurrentUserId());
+                PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+                mSettings.getCurrentUserId());
 
         final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
                 mFileManager.getAllAdditionalInputMethodSubtypes();
@@ -2429,7 +2458,7 @@
             if (!map.containsKey(defaultImiId)) {
                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
                 if (chooseNewDefaultIMELocked()) {
-                    updateFromSettingsLocked();
+                    updateFromSettingsLocked(true);
                 }
             } else {
                 // Double check that the default IME is certainly enabled.
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 47987f1..5462ecc 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static com.android.internal.util.ArrayUtils.appendInt;
@@ -8906,13 +8907,14 @@
         if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT
               || newState == COMPONENT_ENABLED_STATE_ENABLED
               || newState == COMPONENT_ENABLED_STATE_DISABLED
-              || newState == COMPONENT_ENABLED_STATE_DISABLED_USER)) {
+              || newState == COMPONENT_ENABLED_STATE_DISABLED_USER
+              || newState == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
             throw new IllegalArgumentException("Invalid new component state: "
                     + newState);
         }
         PackageSetting pkgSetting;
         final int uid = Binder.getCallingUid();
-        final int permission = mContext.checkCallingPermission(
+        final int permission = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         enforceCrossUserPermission(uid, userId, false, "set enabled");
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 06f11bc..e336524 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@@ -2412,8 +2413,14 @@
             return false;
         }
         PackageUserState ustate = packageSettings.readUserState(userId);
+        if ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0) {
+            if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                return true;
+            }
+        }
         if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED
                 || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_USER
+                || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                 || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled
                     && ustate.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) {
             return false;
diff --git a/services/java/com/android/server/usb/UsbSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java
index 4b2bbfe..f9aaa17 100644
--- a/services/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/java/com/android/server/usb/UsbSettingsManager.java
@@ -364,8 +364,9 @@
         }
 
         @Override
-        public void onPackageChanged(String packageName, int uid, String[] components) {
+        public boolean onPackageChanged(String packageName, int uid, String[] components) {
             handlePackageUpdate(packageName);
+            return false;
         }
 
         @Override