diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index a10cc120ad6c4..edacbb0bb2b4b 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -18,7 +18,6 @@ package android.content.om; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -39,6 +38,7 @@ public final class OverlayInfo implements Parcelable { STATE_NO_IDMAP, STATE_DISABLED, STATE_ENABLED, + STATE_ENABLED_STATIC, STATE_TARGET_UPGRADING, STATE_OVERLAY_UPGRADING, }) @@ -91,7 +91,16 @@ public final class OverlayInfo implements Parcelable { public static final int STATE_OVERLAY_UPGRADING = 5; /** - * Category for theme overlays. + * The overlay package is currently enabled because it is marked as + * 'static'. It cannot be disabled but will change state if for instance + * its target is uninstalled. + */ + public static final int STATE_ENABLED_STATIC = 6; + + /** + * Overlay category: theme. + *

+ * Change how Android (including the status bar, dialogs, ...) looks. */ public static final String CATEGORY_THEME = "android.theme"; @@ -125,6 +134,23 @@ public final class OverlayInfo implements Parcelable { */ public final int userId; + /** + * Priority as read from the manifest. Used if isStatic is true. Not + * intended to be exposed to 3rd party. + * + * @hide + */ + public final int priority; + + /** + * isStatic as read from the manifest. If true, the overlay is + * unconditionally loaded and cannot be unloaded. Not intended to be + * exposed to 3rd party. + * + * @hide + */ + public final boolean isStatic; + /** * Create a new OverlayInfo based on source with an updated state. * @@ -133,17 +159,20 @@ public final class OverlayInfo implements Parcelable { */ public OverlayInfo(@NonNull OverlayInfo source, @State int state) { this(source.packageName, source.targetPackageName, source.category, source.baseCodePath, - state, source.userId); + state, source.userId, source.priority, source.isStatic); } public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName, - @Nullable String category, @NonNull String baseCodePath, int state, int userId) { + @NonNull String category, @NonNull String baseCodePath, int state, int userId, + int priority, boolean isStatic) { this.packageName = packageName; this.targetPackageName = targetPackageName; this.category = category; this.baseCodePath = baseCodePath; this.state = state; this.userId = userId; + this.priority = priority; + this.isStatic = isStatic; ensureValidState(); } @@ -154,6 +183,8 @@ public final class OverlayInfo implements Parcelable { baseCodePath = source.readString(); state = source.readInt(); userId = source.readInt(); + priority = source.readInt(); + isStatic = source.readBoolean(); ensureValidState(); } @@ -173,6 +204,7 @@ public final class OverlayInfo implements Parcelable { case STATE_NO_IDMAP: case STATE_DISABLED: case STATE_ENABLED: + case STATE_ENABLED_STATIC: case STATE_TARGET_UPGRADING: case STATE_OVERLAY_UPGRADING: break; @@ -194,6 +226,8 @@ public final class OverlayInfo implements Parcelable { dest.writeString(baseCodePath); dest.writeInt(state); dest.writeInt(userId); + dest.writeInt(priority); + dest.writeBoolean(isStatic); } public static final Parcelable.Creator CREATOR = @@ -220,6 +254,7 @@ public final class OverlayInfo implements Parcelable { public boolean isEnabled() { switch (state) { case STATE_ENABLED: + case STATE_ENABLED_STATIC: return true; default: return false; @@ -244,6 +279,8 @@ public final class OverlayInfo implements Parcelable { return "STATE_DISABLED"; case STATE_ENABLED: return "STATE_ENABLED"; + case STATE_ENABLED_STATIC: + return "STATE_ENABLED_STATIC"; case STATE_TARGET_UPGRADING: return "STATE_TARGET_UPGRADING"; case STATE_OVERLAY_UPGRADING: diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 36390823bd404..112059daf95e0 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -18,6 +18,7 @@ package com.android.server.om; import static android.content.om.OverlayInfo.STATE_DISABLED; import static android.content.om.OverlayInfo.STATE_ENABLED; +import static android.content.om.OverlayInfo.STATE_ENABLED_STATIC; import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; import static android.content.om.OverlayInfo.STATE_NO_IDMAP; import static android.content.om.OverlayInfo.STATE_OVERLAY_UPGRADING; @@ -63,6 +64,38 @@ final class OverlayManagerServiceImpl { private final String[] mDefaultOverlays; private final OverlayChangeListener mListener; + /** + * Helper method to merge the overlay manager's (as read from overlays.xml) + * and package manager's (as parsed from AndroidManifest.xml files) views + * on overlays. + * + * Both managers are usually in agreement, but especially after an OTA things + * may differ. The package manager is always providing the truth; the overlay + * manager has to adapt. Depending on what has changed about an overlay, we + * should either scrap the overlay manager's previous settings or merge the old + * settings with the new. + */ + private static boolean mustReinitializeOverlay(@NonNull final PackageInfo theTruth, + @Nullable final OverlayInfo oldSettings) { + if (oldSettings == null) { + return true; + } + if (!Objects.equals(theTruth.overlayTarget, oldSettings.targetPackageName)) { + return true; + } + if (theTruth.isStaticOverlayPackage() != oldSettings.isStatic) { + return true; + } + // a change in priority is only relevant for static RROs: specifically, + // a regular RRO should not have its state reset only because a change + // in priority + if (theTruth.isStaticOverlayPackage() && + theTruth.overlayPriority != oldSettings.priority) { + return true; + } + return false; + } + OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager, @NonNull final IdmapManager idmapManager, @NonNull final OverlayManagerSettings settings, @@ -99,42 +132,29 @@ final class OverlayManagerServiceImpl { } } + // Reset overlays if something critical like the target package name + // has changed List overlayPackages = mPackageManager.getOverlayPackages(newUserId); final int overlayPackagesSize = overlayPackages.size(); for (int i = 0; i < overlayPackagesSize; i++) { final PackageInfo overlayPackage = overlayPackages.get(i); final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName); - if (oi == null || !oi.targetPackageName.equals(overlayPackage.overlayTarget)) { - // Reset the overlay if it didn't exist or had the wrong target package. + + if (mustReinitializeOverlay(overlayPackage, oi)) { + // if targetPackageName has changed the package that *used* to + // be the target must also update its assets + if (oi != null) { + packagesToUpdateAssets.add(oi.targetPackageName); + } + mSettings.init(overlayPackage.packageName, newUserId, overlayPackage.overlayTarget, overlayPackage.applicationInfo.getBaseCodePath(), overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority, overlayPackage.overlayCategory); - - if (oi != null) { - // The targetPackageName we have stored doesn't match the overlay's target. - // Queue the old target for an update as well. - packagesToUpdateAssets.add(oi.targetPackageName); - } - } else { - // Update all other components of an overlay that don't require a hard reset. - if (!Objects.equals(oi.category, overlayPackage.overlayCategory)) { - // When changing categories, it is ok just to update our internal state. - mSettings.setCategory(overlayPackage.packageName, newUserId, - overlayPackage.overlayCategory); - } } - try { - updateState(overlayPackage.overlayTarget, overlayPackage.packageName, newUserId, 0); - } catch (OverlayManagerSettings.BadKeyException e) { - Slog.e(TAG, "failed to update settings", e); - mSettings.remove(overlayPackage.packageName, newUserId); - } - - packagesToUpdateAssets.add(overlayPackage.overlayTarget); storedOverlayInfos.remove(overlayPackage.packageName); } @@ -148,6 +168,22 @@ final class OverlayManagerServiceImpl { packagesToUpdateAssets.add(oi.targetPackageName); } + // make sure every overlay's state is up-to-date; this needs to happen + // after old overlays have been removed, or we risk removing a + // legitimate idmap file if a new overlay package has the same apk path + // as the removed overlay package used to have + for (int i = 0; i < overlayPackagesSize; i++) { + final PackageInfo overlayPackage = overlayPackages.get(i); + try { + updateState(overlayPackage.overlayTarget, overlayPackage.packageName, + newUserId, 0); + } catch (OverlayManagerSettings.BadKeyException e) { + Slog.e(TAG, "failed to update settings", e); + mSettings.remove(overlayPackage.packageName, newUserId); + } + packagesToUpdateAssets.add(overlayPackage.overlayTarget); + } + // remove target packages that are not installed final Iterator iter = packagesToUpdateAssets.iterator(); while (iter.hasNext()) { @@ -355,15 +391,13 @@ final class OverlayManagerServiceImpl { try { final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId); - if (!oldOi.targetPackageName.equals(pkg.overlayTarget)) { + if (mustReinitializeOverlay(pkg, oldOi)) { + if (oldOi != null && !oldOi.targetPackageName.equals(pkg.overlayTarget)) { + mListener.onOverlaysChanged(pkg.overlayTarget, userId); + } mSettings.init(packageName, userId, pkg.overlayTarget, pkg.applicationInfo.getBaseCodePath(), pkg.isStaticOverlayPackage(), pkg.overlayPriority, pkg.overlayCategory); - } else { - if (!Objects.equals(oldOi.category, pkg.overlayCategory)) { - // Update the category in-place. - mSettings.setCategory(packageName, userId, pkg.overlayCategory); - } } if (updateState(pkg.overlayTarget, packageName, userId, 0)) { @@ -608,6 +642,8 @@ final class OverlayManagerServiceImpl { if (overlayPackage != null) { modified |= mSettings.setBaseCodePath(overlayPackageName, userId, overlayPackage.applicationInfo.getBaseCodePath()); + modified |= mSettings.setCategory(overlayPackageName, userId, + overlayPackage.overlayCategory); } final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId); @@ -650,6 +686,10 @@ final class OverlayManagerServiceImpl { return STATE_NO_IDMAP; } + if (overlayPackage.isStaticOverlayPackage()) { + return STATE_ENABLED_STATIC; + } + final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId); return enabled ? STATE_ENABLED : STATE_DISABLED; } diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java index e176351fcb765..36bf83dfe92ca 100644 --- a/services/core/java/com/android/server/om/OverlayManagerSettings.java +++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java @@ -309,6 +309,7 @@ final class OverlayManagerSettings { pw.print("mState.............: "); pw.println(OverlayInfo.stateToString(item.getState())); pw.print("mIsEnabled.........: "); pw.println(item.isEnabled()); pw.print("mIsStatic..........: "); pw.println(item.isStatic()); + pw.print("mPriority..........: "); pw.println(item.mPriority); pw.print("mCategory..........: "); pw.println(item.mCategory); pw.decreaseIndent(); @@ -528,7 +529,7 @@ final class OverlayManagerSettings { private OverlayInfo getOverlayInfo() { if (mCache == null) { mCache = new OverlayInfo(mPackageName, mTargetPackageName, mCategory, mBaseCodePath, - mState, mUserId); + mState, mUserId, mPriority, mIsStatic); } return mCache; } diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java index 54bb115c54052..d576d330c4a83 100644 --- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java +++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java @@ -126,6 +126,7 @@ final class OverlayManagerShellCommand extends ShellCommand { final OverlayInfo oi = overlaysForTarget.get(i); String status; switch (oi.state) { + case OverlayInfo.STATE_ENABLED_STATIC: case OverlayInfo.STATE_ENABLED: status = "[x]"; break;