From f6e816f08ee45c71435c26f3656e4c488656e90f Mon Sep 17 00:00:00 2001 From: Yurii Zubrytskyi <zyy@google.com> Date: Wed, 1 Nov 2023 19:05:46 -0700 Subject: [PATCH] Runtime: fix the theme colors on device reset When launching an activity we may handle the launch before applying the application info update that carries new overlay paths for the app. In this case the activity will have enough time to load the resources with no overlays applied, and use e.g. wrong accent colors. This change makes sure we apply pending application info updates on any activity launch, and that the resource paths are up to date on the very first Resources object creation. Bug: 299064578 Test: manual, factory reset for 8a, 10x (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7230cdf9d79e634a03eb126f89d0bf2591dccb8a) Merged-In: I501d9ce39f6e0437484632c85bf2773fe4a16474 Change-Id: I501d9ce39f6e0437484632c85bf2773fe4a16474 --- core/java/android/app/ActivityThread.java | 49 +++++++++++++++++++++-- core/java/android/app/LoadedApk.java | 13 +++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f4caef0f2553..016d7214bda9 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -359,6 +359,15 @@ public final class ActivityThread extends ClientTransactionHandler /** Maps from activity token to the pending override configuration. */ @GuardedBy("mPendingOverrideConfigs") private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>(); + + /** + * A queue of pending ApplicationInfo updates. In case when we get a concurrent update + * this queue allows us to only apply the latest object, and it can be applied on demand + * instead of waiting for the handler thread to reach the scheduled callback. + */ + @GuardedBy("mResourcesManager") + private final ArrayMap<String, ApplicationInfo> mPendingAppInfoUpdates = new ArrayMap<>(); + /** The activities to be truly destroyed (not include relaunch). */ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed = Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>()); @@ -1260,9 +1269,19 @@ public final class ActivityThread extends ClientTransactionHandler } public void scheduleApplicationInfoChanged(ApplicationInfo ai) { + synchronized (mResourcesManager) { + var oldAi = mPendingAppInfoUpdates.put(ai.packageName, ai); + if (oldAi != null && oldAi.createTimestamp > ai.createTimestamp) { + Slog.w(TAG, "Skipping application info changed for obsolete AI with TS " + + ai.createTimestamp + " < already pending TS " + + oldAi.createTimestamp); + mPendingAppInfoUpdates.put(ai.packageName, oldAi); + return; + } + } mResourcesManager.appendPendingAppInfoUpdate(new String[]{ai.sourceDir}, ai); - mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai); - sendMessage(H.APPLICATION_INFO_CHANGED, ai); + mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai.packageName); + sendMessage(H.APPLICATION_INFO_CHANGED, ai.packageName); } public void updateTimeZone() { @@ -2437,7 +2456,7 @@ public final class ActivityThread extends ClientTransactionHandler break; } case APPLICATION_INFO_CHANGED: - handleApplicationInfoChanged((ApplicationInfo) msg.obj); + applyPendingApplicationInfoChanges((String) msg.obj); break; case RUN_ISOLATED_ENTRY_POINT: handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1, @@ -3922,7 +3941,8 @@ public final class ActivityThread extends ClientTransactionHandler mProfiler.startProfiling(); } - // Make sure we are running with the most recent config. + // Make sure we are running with the most recent config and resource paths. + applyPendingApplicationInfoChanges(r.activityInfo.packageName); mConfigurationController.handleConfigurationChanged(null, null); updateDeviceIdForNonUIContexts(deviceId); @@ -6248,6 +6268,17 @@ public final class ActivityThread extends ClientTransactionHandler r.mLastReportedWindowingMode = newWindowingMode; } + private void applyPendingApplicationInfoChanges(String packageName) { + final ApplicationInfo ai; + synchronized (mResourcesManager) { + ai = mPendingAppInfoUpdates.remove(packageName); + } + if (ai == null) { + return; + } + handleApplicationInfoChanged(ai); + } + /** * Updates the application info. * @@ -6273,6 +6304,16 @@ public final class ActivityThread extends ClientTransactionHandler apk = ref != null ? ref.get() : null; ref = mResourcePackages.get(ai.packageName); resApk = ref != null ? ref.get() : null; + for (ActivityClientRecord ar : mActivities.values()) { + if (ar.activityInfo.applicationInfo.packageName.equals(ai.packageName)) { + ar.activityInfo.applicationInfo = ai; + if (apk != null || resApk != null) { + ar.packageInfo = apk != null ? apk : resApk; + } else { + apk = ar.packageInfo; + } + } + } } if (apk != null) { diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index b5efb73225d6..f092ce2363aa 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -343,7 +343,9 @@ public final class LoadedApk { */ public void updateApplicationInfo(@NonNull ApplicationInfo aInfo, @Nullable List<String> oldPaths) { - setApplicationInfo(aInfo); + if (!setApplicationInfo(aInfo)) { + return; + } final List<String> newPaths = new ArrayList<>(); makePaths(mActivityThread, aInfo, newPaths); @@ -388,7 +390,13 @@ public final class LoadedApk { mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader); } - private void setApplicationInfo(ApplicationInfo aInfo) { + private boolean setApplicationInfo(ApplicationInfo aInfo) { + if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) { + Slog.w(TAG, "New application info for package " + aInfo.packageName + + " is out of date with TS " + aInfo.createTimestamp + " < the current TS " + + mApplicationInfo.createTimestamp); + return false; + } final int myUid = Process.myUid(); aInfo = adjustNativeLibraryPaths(aInfo); mApplicationInfo = aInfo; @@ -411,6 +419,7 @@ public final class LoadedApk { if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) { mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies); } + return true; } void setSdkSandboxStorage(@Nullable String sdkSandboxClientAppVolumeUuid, -- GitLab