From db587c6e5d3d23248b09252340d911b5c688a7c9 Mon Sep 17 00:00:00 2001 From: Rhed Jao Date: Mon, 19 Jul 2021 18:35:59 +0800 Subject: [PATCH] Add multiple components support for component enabled setting updates This cl creates a new API setComponentsEnabledSetting in PackageManager. It accepts a list of component changes that applies them all atomically in order to improve performance and minimize the risk of errors for the applications. Bug: 189883959 Test: atest PackageManagerTest Change-Id: I64d9f46198e26e001e79ca9c198c26929adf8596 --- core/api/current.txt | 11 + .../app/ApplicationPackageManager.java | 9 + .../android/content/pm/IPackageManager.aidl | 6 + .../android/content/pm/PackageManager.aidl | 1 + .../android/content/pm/PackageManager.java | 243 +++++++++ .../server/pm/PackageManagerService.java | 485 +++++++++++------- 6 files changed, 572 insertions(+), 183 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 396c08039e666..668e254244dfe 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12633,6 +12633,7 @@ package android.content.pm { method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int); method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean setAutoRevokeWhitelisted(@NonNull String, boolean); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int); + method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setComponentEnabledSettings(@NonNull java.util.List); method public abstract void setInstallerPackageName(@NonNull String, @Nullable String); method public void setMimeGroup(@NonNull String, @NonNull java.util.Set); method public abstract void updateInstantAppCookie(@Nullable byte[]); @@ -12829,6 +12830,16 @@ package android.content.pm { field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff } + public static final class PackageManager.ComponentEnabledSetting implements android.os.Parcelable { + ctor public PackageManager.ComponentEnabledSetting(@NonNull android.content.ComponentName, int, int); + method public int describeContents(); + method @Nullable public android.content.ComponentName getComponentName(); + method public int getEnabledFlags(); + method public int getEnabledState(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public static class PackageManager.NameNotFoundException extends android.util.AndroidException { ctor public PackageManager.NameNotFoundException(); ctor public PackageManager.NameNotFoundException(String); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 7e4f9b4063757..57de588f38843 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2806,6 +2806,15 @@ public class ApplicationPackageManager extends PackageManager { } } + @Override + public void setComponentEnabledSettings(List settings) { + try { + mPM.setComponentEnabledSettings(settings, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override public int getComponentEnabledSetting(ComponentName componentName) { try { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index a4ff18a5e85d6..86030fdc1d72f 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -40,6 +40,7 @@ import android.content.pm.KeySet; import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ComponentEnabledSetting; import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.PermissionGroupInfo; @@ -358,6 +359,11 @@ interface IPackageManager { void setComponentEnabledSetting(in ComponentName componentName, in int newState, in int flags, int userId); + /** + * As per {@link android.content.pm.PackageManager#setComponentEnabledSettings}. + */ + void setComponentEnabledSettings(in List settings, int userId); + /** * As per {@link android.content.pm.PackageManager#getComponentEnabledSetting}. */ diff --git a/core/java/android/content/pm/PackageManager.aidl b/core/java/android/content/pm/PackageManager.aidl index 31365a19f2860..87bfa54571f7d 100644 --- a/core/java/android/content/pm/PackageManager.aidl +++ b/core/java/android/content/pm/PackageManager.aidl @@ -18,3 +18,4 @@ package android.content.pm; parcelable PackageManager.Property; +parcelable PackageManager.ComponentEnabledSetting; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a5cd331de4ff4..dab3e418f4f52 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -83,6 +83,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.DataClass; import dalvik.system.VMRuntime; @@ -379,6 +380,219 @@ public abstract class PackageManager { }; } + /** + * The class containing the enabled setting of a package component. + *

+ * This is used by the {@link #setComponentEnabledSettings(List)} to support the batch updates + * of the enabled settings of components. + * + * @see #setComponentEnabledSettings(List) + */ + @DataClass(genConstructor = false) + public static final class ComponentEnabledSetting implements Parcelable { + /** + * The package name of the application to enable the setting. + */ + private final @Nullable String mPackageName; + + /** + * The component name of the application to enable the setting. + */ + private final @Nullable ComponentName mComponentName; + + /** + * The new enabled state + */ + private final @EnabledState int mEnabledState; + + /** + * The optional behavior flag + */ + private final @EnabledFlags int mEnabledFlags; + + /** + * Create an instance of the ComponentEnabledSetting for the component level's enabled + * setting update. + * + * @param componentName The component name to update the enabled setting. + * @param newState The new enabled state. + * @param flags The optional behavior flags. + */ + public ComponentEnabledSetting(@NonNull ComponentName componentName, + @EnabledState int newState, @EnabledFlags int flags) { + Objects.nonNull(componentName); + mPackageName = null; + mComponentName = componentName; + mEnabledState = newState; + mEnabledFlags = flags; + } + + /** + * Create an instance of the ComponentEnabledSetting for the application level's enabled + * setting update. + * + * @param packageName The package name to update the enabled setting. + * @param newState The new enabled state. + * @param flags The optional behavior flags. + * @hide + */ + public ComponentEnabledSetting(@NonNull String packageName, + @EnabledState int newState, @EnabledFlags int flags) { + Objects.nonNull(packageName); + mPackageName = packageName; + mComponentName = null; + mEnabledState = newState; + mEnabledFlags = flags; + } + + /** + * Returns the package name of the setting. + * + * @return the package name. + * @hide + */ + public @NonNull String getPackageName() { + if (isComponent()) { + return mComponentName.getPackageName(); + } + return mPackageName; + } + + /** + * Returns the component class name of the setting. + * + * @return the class name. + * @hide + */ + public @Nullable String getClassName() { + if (isComponent()) { + return mComponentName.getClassName(); + } + return null; + } + + /** + * Whether or not this is for the component level's enabled setting update. + * + * @return {@code true} if it's the component level enabled setting update. + * @hide + */ + public boolean isComponent() { + return mComponentName != null; + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageManager.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * The component name of the application to enable the setting. + */ + @DataClass.Generated.Member + public @Nullable ComponentName getComponentName() { + return mComponentName; + } + + /** + * The new enabled state + */ + @DataClass.Generated.Member + public @EnabledState int getEnabledState() { + return mEnabledState; + } + + /** + * The optional behavior flag + */ + @DataClass.Generated.Member + public @EnabledFlags int getEnabledFlags() { + return mEnabledFlags; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mPackageName != null) flg |= 0x1; + if (mComponentName != null) flg |= 0x2; + dest.writeByte(flg); + if (mPackageName != null) dest.writeString(mPackageName); + if (mComponentName != null) dest.writeTypedObject(mComponentName, flags); + dest.writeInt(mEnabledState); + dest.writeInt(mEnabledFlags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ComponentEnabledSetting(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + String packageName = (flg & 0x1) == 0 ? null : in.readString(); + ComponentName componentName = (flg & 0x2) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR); + int enabledState = in.readInt(); + int enabledFlags = in.readInt(); + + this.mPackageName = packageName; + this.mComponentName = componentName; + this.mEnabledState = enabledState; + com.android.internal.util.AnnotationValidations.validate( + EnabledState.class, null, mEnabledState); + this.mEnabledFlags = enabledFlags; + com.android.internal.util.AnnotationValidations.validate( + EnabledFlags.class, null, mEnabledFlags); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public ComponentEnabledSetting[] newArray(int size) { + return new ComponentEnabledSetting[size]; + } + + @Override + public ComponentEnabledSetting createFromParcel(@NonNull Parcel in) { + return new ComponentEnabledSetting(in); + } + }; + + @DataClass.Generated( + time = 1628668290863L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/content/pm/PackageManager.java", + inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable android.content.ComponentName mComponentName\nprivate final @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate final @android.content.pm.PackageManager.EnabledFlags int mEnabledFlags\npublic @android.annotation.NonNull java.lang.String getPackageName()\npublic @android.annotation.Nullable java.lang.String getClassName()\npublic boolean isComponent()\nclass ComponentEnabledSetting extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + + } + /** * Listener for changes in permissions granted to a UID. * @@ -7675,6 +7889,9 @@ public abstract class PackageManager { * This setting will override any enabled state which may have been set by the component in its * manifest. * + *

Consider using {@link #setComponentEnabledSettings(List)} if multiple components need to + * be updated atomically. + * * @param componentName The component to enable * @param newState The new enabled state for the component. * @param flags Optional behavior flags. @@ -7684,6 +7901,32 @@ public abstract class PackageManager { public abstract void setComponentEnabledSetting(@NonNull ComponentName componentName, @EnabledState int newState, @EnabledFlags int flags); + /** + * Set the enabled settings for package components such as activities, receivers, services and + * providers. This setting will override any enabled state which may have been set by the + * component in its manifest. + * + *

This api accepts a list of component changes, and applies them all atomically. The + * application can use this api if components have dependencies and need to be updated + * atomically. + * + *

The permission is not required if target components are running under the same uid with + * the caller. + * + * @param settings The list of component enabled settings to update. Note that an + * {@link IllegalArgumentException} is thrown if the duplicated component name + * is in the list or there's a conflict {@link #DONT_KILL_APP} flag between + * different components in the same package. + * + * @see #setComponentEnabledSetting(ComponentName, int, int) + */ + @RequiresPermission(value = android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, + conditional = true) + public void setComponentEnabledSettings(@NonNull List settings) { + throw new UnsupportedOperationException("setComponentEnabledSettings not implemented" + + "in subclass"); + } + /** * Return the enabled setting for a package component (activity, * receiver, service, provider). This returns the last value set by diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d2c15ab23c83a..d69ac52d3d792 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -177,6 +177,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ComponentEnabledSetting; import android.content.pm.PackageManager.ComponentType; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageManager.ModuleInfoFlags; @@ -19900,7 +19901,9 @@ public class PackageManagerService extends IPackageManager.Stub if (callingPackage == null) { callingPackage = Integer.toString(Binder.getCallingUid()); } - setEnabledSetting(appPackageName, null, newState, flags, userId, callingPackage); + + setEnabledSettings(List.of(new ComponentEnabledSetting(appPackageName, newState, flags)), + userId, callingPackage); } @Override @@ -19997,216 +20000,270 @@ public class PackageManagerService extends IPackageManager.Stub public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, int userId) { if (!mUserManager.exists(userId)) return; - setEnabledSetting(componentName.getPackageName(), - componentName.getClassName(), newState, flags, userId, null); + + setEnabledSettings(List.of(new ComponentEnabledSetting(componentName, newState, flags)), + userId, null /* callingPackage */); } - private void setEnabledSetting(final String packageName, String className, int newState, - final int flags, int userId, String callingPackage) { - 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_UNTIL_USED)) { - throw new IllegalArgumentException("Invalid new component state: " - + newState); + @Override + public void setComponentEnabledSettings(List settings, int userId) { + if (!mUserManager.exists(userId)) return; + if (settings == null || settings.isEmpty()) { + throw new IllegalArgumentException("The list of enabled settings is empty"); } + + setEnabledSettings(settings, userId, null /* callingPackage */); + } + + private void setEnabledSettings(List settings, int userId, + String callingPackage) { final int callingUid = Binder.getCallingUid(); - final int permission; - if (callingUid == Process.SYSTEM_UID) { - permission = PackageManager.PERMISSION_GRANTED; - } else { - permission = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); - } enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, true /* checkShell */, "set enabled"); - final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); - boolean sendNow = false; - boolean isApp = (className == null); - String componentName = isApp ? packageName : className; - ArrayList components; - final boolean isCallerTargetApp = ArrayUtils.contains( - getPackagesForUid(callingUid), packageName); - final PackageSetting pkgSetting; + final int targetSize = settings.size(); + for (int i = 0; i < targetSize; i++) { + final int newState = settings.get(i).getEnabledState(); + 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_UNTIL_USED)) { + throw new IllegalArgumentException("Invalid new component state: " + newState); + } + } + if (targetSize > 1) { + final ArraySet checkDuplicatedPackage = new ArraySet<>(); + final ArraySet checkDuplicatedComponent = new ArraySet<>(); + final ArrayMap checkConflictFlag = new ArrayMap<>(); + for (int i = 0; i < targetSize; i++) { + final ComponentEnabledSetting setting = settings.get(i); + final String packageName = setting.getPackageName(); + if (setting.isComponent()) { + final ComponentName componentName = setting.getComponentName(); + if (checkDuplicatedComponent.contains(componentName)) { + throw new IllegalArgumentException("The component " + componentName + + " is duplicated"); + } + checkDuplicatedComponent.add(componentName); + + // check if there is a conflict of the DONT_KILL_APP flag between components + // in the package + final Integer enabledFlags = checkConflictFlag.get(packageName); + if (enabledFlags == null) { + checkConflictFlag.put(packageName, setting.getEnabledFlags()); + } else if ((enabledFlags & PackageManager.DONT_KILL_APP) + != (setting.getEnabledFlags() & PackageManager.DONT_KILL_APP)) { + throw new IllegalArgumentException("A conflict of the DONT_KILL_APP flag " + + "between components in the package " + packageName); + } + } else { + if (checkDuplicatedPackage.contains(packageName)) { + throw new IllegalArgumentException("The package " + packageName + + " is duplicated"); + } + checkDuplicatedPackage.add(packageName); + } + } + } + + final boolean allowedByPermission = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE) == PERMISSION_GRANTED; + final boolean[] updateAllowed = new boolean[targetSize]; + Arrays.fill(updateAllowed, true); + + final Map pkgSettings = new ArrayMap<>(targetSize); // reader synchronized (mLock) { - pkgSetting = mSettings.getPackageLPr(packageName); - // Limit who can change which apps - if (!isCallerTargetApp) { - // Don't allow apps that don't have permission to modify other apps + // Checks for target packages + for (int i = 0; i < targetSize; i++) { + final ComponentEnabledSetting setting = settings.get(i); + final String packageName = setting.getPackageName(); + if (pkgSettings.containsKey(packageName)) { + // this package has verified + continue; + } + final boolean isCallerTargetApp = ArrayUtils.contains( + getPackagesForUid(callingUid), packageName); + final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); + // Limit who can change which apps + if (!isCallerTargetApp) { + // Don't allow apps that don't have permission to modify other apps + if (!allowedByPermission + || shouldFilterApplicationLocked(pkgSetting, callingUid, userId)) { + throw new SecurityException("Attempt to change component state; " + + "pid=" + Binder.getCallingPid() + + ", uid=" + callingUid + + (!setting.isComponent() ? ", package=" + packageName + : ", component=" + setting.getComponentName())); + } + // Don't allow changing protected packages. + if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + throw new SecurityException( + "Cannot disable a protected package: " + packageName); + } + } + if (pkgSetting == null) { + throw new IllegalArgumentException(setting.isComponent() + ? "Unknown component: " + setting.getComponentName() + : "Unknown package: " + packageName); + } + if (callingUid == Process.SHELL_UID + && (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) { + // Shell can only change whole packages between ENABLED and DISABLED_USER states + // unless it is a test package. + final int oldState = pkgSetting.getEnabled(userId); + final int newState = setting.getEnabledState(); + if (!setting.isComponent() + && + (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 " + + setting.getComponentName() + " to " + newState); + } + } + pkgSettings.put(packageName, pkgSetting); + } + // Checks for target components + for (int i = 0; i < targetSize; i++) { + final ComponentEnabledSetting setting = settings.get(i); + // skip if it's application + if (!setting.isComponent()) continue; + + // Only allow apps with CHANGE_COMPONENT_ENABLED_STATE permission to change hidden + // app details activity + final String packageName = setting.getPackageName(); + final String className = setting.getClassName(); if (!allowedByPermission - || shouldFilterApplicationLocked(pkgSetting, callingUid, userId)) { - throw new SecurityException( - "Attempt to change component state; " - + "pid=" + Binder.getCallingPid() - + ", uid=" + callingUid - + (className == null - ? ", package=" + packageName - : ", component=" + packageName + "/" + className)); + && PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(className)) { + throw new SecurityException("Cannot disable a system-generated component"); } - // Don't allow changing protected packages. - if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { - throw new SecurityException( - "Cannot disable a protected package: " + packageName); - } - } - if (pkgSetting == null) { - if (className == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - throw new IllegalArgumentException( - "Unknown component: " + packageName + "/" + className); - } - } - - // Only allow apps with CHANGE_COMPONENT_ENABLED_STATE permission to change hidden - // app details activity - if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(className) - && !allowedByPermission) { - throw new SecurityException("Cannot disable a system-generated component"); - } - - synchronized (mLock) { - if (callingUid == Process.SHELL_UID - && (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) { - // Shell can only change whole packages between ENABLED and DISABLED_USER states - // unless it is a test package. - 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 - synchronized (mLock) { - if (pkgSetting.getEnabled(userId) == newState) { - // Nothing to do - return; - } - } - // If we're enabling a system stub, there's a little more work to do. - // Prior to enabling the package, we need to decompress the APK(s) to the - // data partition and then replace the version on the system partition. - final AndroidPackage deletedPkg = pkgSetting.pkg; - final boolean isSystemStub = (deletedPkg != null) - && deletedPkg.isStub() - && deletedPkg.isSystem(); - if (isSystemStub - && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) { - if (!enableCompressedPackage(deletedPkg, pkgSetting)) { - return; - } - } - if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT - || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { - // Don't care about who enables an app. - callingPackage = null; - } - synchronized (mLock) { - pkgSetting.setEnabled(newState, userId, callingPackage); - if ((newState == COMPONENT_ENABLED_STATE_DISABLED_USER - || newState == COMPONENT_ENABLED_STATE_DISABLED) - && checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) - == PERMISSION_GRANTED) { - // This app should not generally be allowed to get disabled by the UI, but if it - // ever does, we don't want to end up with some of the user's apps permanently - // suspended. - unsuspendForSuspendingPackage(packageName, userId); - removeAllDistractingPackageRestrictions(userId); - } - } - } else { - synchronized (mLock) { - // We're dealing with a component level state change - // First, verify that this is a valid class name. - AndroidPackage pkg = pkgSetting.pkg; + // Verify that this is a valid class name. + final AndroidPackage pkg = pkgSettings.get(packageName).getPkg(); if (pkg == null || !AndroidPackageUtils.hasComponentClassName(pkg, className)) { - if (pkg != null && - pkg.getTargetSdkVersion() >= - Build.VERSION_CODES.JELLY_BEAN) { + if (pkg != null + && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.JELLY_BEAN) { throw new IllegalArgumentException("Component class " + className + " does not exist in " + packageName); } else { Slog.w(TAG, "Failed setComponentEnabledSetting: component class " + className + " does not exist in " + packageName); + updateAllowed[i] = false; } } - switch (newState) { - case COMPONENT_ENABLED_STATE_ENABLED: - if (!pkgSetting.enableComponentLPw(className, userId)) { - return; - } - break; - case COMPONENT_ENABLED_STATE_DISABLED: - if (!pkgSetting.disableComponentLPw(className, userId)) { - return; - } - break; - case COMPONENT_ENABLED_STATE_DEFAULT: - if (!pkgSetting.restoreComponentLPw(className, userId)) { - return; - } - break; - default: - Slog.e(TAG, "Invalid new component state: " + newState); - return; - } } } + + // More work for application enabled setting updates + for (int i = 0; i < targetSize; i++) { + final ComponentEnabledSetting setting = settings.get(i); + // skip if it's component + if (setting.isComponent()) continue; + + final PackageSetting pkgSetting = pkgSettings.get(setting.getPackageName()); + final int newState = setting.getEnabledState(); + synchronized (mLock) { + if (pkgSetting.getEnabled(userId) == newState) { + // Nothing to do + updateAllowed[i] = false; + continue; + } + } + // If we're enabling a system stub, there's a little more work to do. + // Prior to enabling the package, we need to decompress the APK(s) to the + // data partition and then replace the version on the system partition. + final AndroidPackage deletedPkg = pkgSetting.getPkg(); + final boolean isSystemStub = (deletedPkg != null) + && deletedPkg.isStub() + && deletedPkg.isSystem(); + if (isSystemStub + && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) { + if (!enableCompressedPackage(deletedPkg, pkgSetting)) { + Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable " + + "commpressed package " + setting.getPackageName()); + updateAllowed[i] = false; + continue; + } + } + } + + // packageName -> list of components to send broadcasts now + final ArrayMap> sendNowBroadcasts = new ArrayMap<>(targetSize); synchronized (mLock) { - if ((flags & PackageManager.SYNCHRONOUS) != 0) { + boolean scheduleBroadcastMessage = false; + boolean isSynchronous = false; + boolean anyChanged = false; + + for (int i = 0; i < targetSize; i++) { + if (!updateAllowed[i]) { + continue; + } + // update enabled settings + final ComponentEnabledSetting setting = settings.get(i); + final String packageName = setting.getPackageName(); + if (!setEnabledSettingInternalLocked(pkgSettings.get(packageName), setting, + userId, callingPackage)) { + continue; + } + anyChanged = true; + + if ((setting.getEnabledFlags() & PackageManager.SYNCHRONOUS) != 0) { + isSynchronous = true; + } + // collect broadcast list for the package + final String componentName = setting.isComponent() + ? setting.getClassName() : packageName; + ArrayList componentList = sendNowBroadcasts.get(packageName); + if (componentList == null) { + componentList = mPendingBroadcasts.get(userId, packageName); + } + final boolean newPackage = componentList == null; + if (newPackage) { + componentList = new ArrayList<>(); + } + if (!componentList.contains(componentName)) { + componentList.add(componentName); + } + if ((setting.getEnabledFlags() & PackageManager.DONT_KILL_APP) == 0) { + sendNowBroadcasts.put(packageName, componentList); + // Purge entry from pending broadcast list if another one exists already + // since we are sending one right away. + mPendingBroadcasts.remove(userId, packageName); + } else { + if (newPackage) { + mPendingBroadcasts.put(userId, packageName, componentList); + } + scheduleBroadcastMessage = true; + } + } + if (!anyChanged) { + // nothing changed, return immediately + return; + } + + if (isSynchronous) { flushPackageRestrictionsAsUserInternalLocked(userId); } else { scheduleWritePackageRestrictionsLocked(userId); } - updateSequenceNumberLP(pkgSetting, new int[] { userId }); - final long callingId = Binder.clearCallingIdentity(); - try { - updateInstantAppInstallerLocked(packageName); - } finally { - Binder.restoreCallingIdentity(callingId); - } - components = mPendingBroadcasts.get(userId, packageName); - final boolean newPackage = components == null; - if (newPackage) { - components = new ArrayList<>(); - } - if (!components.contains(componentName)) { - components.add(componentName); - } - if ((flags&PackageManager.DONT_KILL_APP) == 0) { - sendNow = true; - // Purge entry from pending broadcast list if another one exists already - // since we are sending one right away. - mPendingBroadcasts.remove(userId, packageName); - } else { - if (newPackage) { - mPendingBroadcasts.put(userId, packageName, components); - } + if (scheduleBroadcastMessage) { if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) { // Schedule a message - if it has been a "reasonably long time" since the // service started, send the broadcast with a delay of one second to avoid // delayed reactions from the receiver, else keep the default ten second delay // to avoid extreme thrashing on service startup. final long broadcastDelay = SystemClock.uptimeMillis() > mServiceStartWithDelay - ? BROADCAST_DELAY - : BROADCAST_DELAY_DURING_STARTUP; + ? BROADCAST_DELAY + : BROADCAST_DELAY_DURING_STARTUP; mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, broadcastDelay); } } @@ -20214,16 +20271,78 @@ public class PackageManagerService extends IPackageManager.Stub final long callingId = Binder.clearCallingIdentity(); try { - if (sendNow) { - int packageUid = UserHandle.getUid(userId, pkgSetting.appId); - sendPackageChangedBroadcast(packageName, - (flags & PackageManager.DONT_KILL_APP) != 0, components, packageUid, null); + for (int i = 0; i < sendNowBroadcasts.size(); i++) { + final String packageName = sendNowBroadcasts.keyAt(i); + final ArrayList components = sendNowBroadcasts.valueAt(i); + final int packageUid = UserHandle.getUid( + userId, pkgSettings.get(packageName).appId); + sendPackageChangedBroadcast(packageName, false /* dontKillApp */, + components, packageUid, null /* reason */); } } finally { Binder.restoreCallingIdentity(callingId); } } + private boolean setEnabledSettingInternalLocked(PackageSetting pkgSetting, + ComponentEnabledSetting setting, int userId, String callingPackage) { + final int newState = setting.getEnabledState(); + final String packageName = setting.getPackageName(); + boolean success = false; + if (!setting.isComponent()) { + // We're dealing with an application/package level state change + if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT + || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + // Don't care about who enables an app. + callingPackage = null; + } + pkgSetting.setEnabled(newState, userId, callingPackage); + if ((newState == COMPONENT_ENABLED_STATE_DISABLED_USER + || newState == COMPONENT_ENABLED_STATE_DISABLED) + && checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) + == PERMISSION_GRANTED) { + // This app should not generally be allowed to get disabled by the UI, but + // if it ever does, we don't want to end up with some of the user's apps + // permanently suspended. + unsuspendForSuspendingPackage(packageName, userId); + removeAllDistractingPackageRestrictions(userId); + } + success = true; + } else { + // We're dealing with a component level state change + final String className = setting.getClassName(); + switch (newState) { + case COMPONENT_ENABLED_STATE_ENABLED: + success = pkgSetting.enableComponentLPw(className, userId); + break; + case COMPONENT_ENABLED_STATE_DISABLED: + success = pkgSetting.disableComponentLPw(className, userId); + break; + case COMPONENT_ENABLED_STATE_DEFAULT: + success = pkgSetting.restoreComponentLPw(className, userId); + break; + default: + Slog.e(TAG, "Failed setComponentEnabledSetting: component " + + packageName + "/" + className + + " requested an invalid new component state: " + newState); + break; + } + } + if (!success) { + return false; + } + + updateSequenceNumberLP(pkgSetting, new int[] { userId }); + final long callingId = Binder.clearCallingIdentity(); + try { + updateInstantAppInstallerLocked(packageName); + } finally { + Binder.restoreCallingIdentity(callingId); + } + + return true; + } + @WorkerThread @Override public void flushPackageRestrictionsAsUser(int userId) {