From fd7adedebf88427162a3ce27fcc9cfd3893c869d Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 22 Jan 2013 17:10:23 -0800 Subject: 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 --- api/current.txt | 2 + cmds/pm/src/com/android/commands/pm/Pm.java | 8 +++ core/java/android/content/Intent.java | 6 +- core/java/android/content/pm/PackageManager.java | 21 +++++++ core/java/android/content/pm/PackageParser.java | 66 +++++++++++----------- .../android/internal/content/PackageMonitor.java | 41 ++++++++++++-- .../internal/inputmethod/InputMethodUtils.java | 2 +- .../android/server/InputMethodManagerService.java | 43 +++++++++++--- .../android/server/pm/PackageManagerService.java | 6 +- services/java/com/android/server/pm/Settings.java | 7 +++ .../com/android/server/usb/UsbSettingsManager.java | 3 +- 11 files changed, 154 insertions(+), 51 deletions(-) diff --git a/api/current.txt b/api/current.txt index d367a00bc347..199541604d84 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6627,6 +6627,7 @@ package android.content.pm { 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 @@ package android.content.pm { 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 39539b4ec3ca..f0e337017ed2 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 @@ public final class Pm { 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 @@ public final class Pm { 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 @@ public final class Pm { 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 89b1bbd2ee4e..b5349fdc95fc 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1588,7 +1588,7 @@ public class Intent implements Parcelable, Cloneable { * @@ -2969,7 +2969,9 @@ public class Intent implements Parcelable, Cloneable { /** * 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 a69f2202ca28..d80598c2a602 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -174,6 +174,14 @@ public abstract class PackageManager { */ 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 @@ -264,6 +272,19 @@ public abstract class PackageManager { */ 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 can not 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 diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 3e8c2a8572d1..e1887bc64452 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3527,29 +3527,45 @@ public class PackageParser { 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 @@ public class PackageParser { 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 20ecaceade13..424c19be6ec7 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -153,8 +153,33 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { 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 abstract class PackageMonitor extends android.content.BroadcastReceiver { */ 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 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { 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 4d41e4284999..3d7e1ffc1277 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -536,7 +536,7 @@ public class InputMethodUtils { } } - 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 593b9bf99920..0f14265e1334 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -382,6 +382,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private boolean mInputBoundToKeyguard; class SettingsObserver extends ContentObserver { + String mLastEnabled = ""; + SettingsObserver(Handler handler) { super(handler); ContentResolver resolver = mContext.getContentResolver(); @@ -395,7 +397,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (changed) { - updateFromSettingsLocked(); + updateFromSettingsLocked(false); } } } @@ -674,7 +682,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return false; } - void updateFromSettingsLocked() { + void updateFromSettingsLocked(boolean enabledMayChange) { + if (enabledMayChange) { + List enabled = mSettings.getEnabledInputMethodListLocked(); + for (int i=0; i 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> additionalSubtypes = mFileManager.getAllAdditionalInputMethodSubtypes(); @@ -2429,7 +2458,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 47987f167eb1..5462ecce3193 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.GRANT_REVOKE_PERMISSIONS; 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 06f11bc9a4da..e33652442c27 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -18,6 +18,7 @@ package com.android.server.pm; 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 @@ final class Settings { 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 4b2bbfe0006b..f9aaa17ba116 100644 --- a/services/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/java/com/android/server/usb/UsbSettingsManager.java @@ -364,8 +364,9 @@ class UsbSettingsManager { } @Override - public void onPackageChanged(String packageName, int uid, String[] components) { + public boolean onPackageChanged(String packageName, int uid, String[] components) { handlePackageUpdate(packageName); + return false; } @Override -- cgit v1.2.3-59-g8ed1b