diff options
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 12 | ||||
| -rw-r--r-- | core/java/android/app/ApplicationPackageManager.java | 3 | ||||
| -rw-r--r-- | core/java/android/app/ContextImpl.java | 58 | ||||
| -rw-r--r-- | core/java/android/app/LoadedApk.java | 5 | ||||
| -rw-r--r-- | core/java/android/app/ResourcesManager.java | 434 | ||||
| -rw-r--r-- | core/java/android/app/WindowTokenClient.java | 3 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageParser.java | 3 | ||||
| -rw-r--r-- | core/java/android/content/res/ResourcesKey.java | 21 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/activity/ActivityThreadTest.java | 49 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/content/res/ResourcesManagerTest.java | 111 |
10 files changed, 509 insertions, 190 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d67b98620f37..f6b045349219 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2242,9 +2242,9 @@ public final class ActivityThread extends ClientTransactionHandler { * Resources if one has already been created. */ Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, - String[] libDirs, int displayId, LoadedApk pkgInfo) { + String[] libDirs, LoadedApk pkgInfo) { return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs, - displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null); + null, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null); } @UnsupportedAppUsage @@ -5692,8 +5692,7 @@ public final class ActivityThread extends ClientTransactionHandler { // many places. final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull( amOverrideConfig, contextThemeWrapperOverrideConfig); - mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, - displayId, movedToDifferentDisplay); + mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId); activity.mConfigChangeFlags = 0; activity.mCurrentConfig = new Configuration(newConfig); @@ -6014,6 +6013,11 @@ public final class ActivityThread extends ClientTransactionHandler { r.mPendingOverrideConfig = null; } + if (displayId == INVALID_DISPLAY) { + // If INVALID_DISPLAY is passed assume that the activity should keep its current + // display. + displayId = r.activity.getDisplayId(); + } final boolean movedToDifferentDisplay = isDifferentDisplay(r.activity, displayId); if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig) && !movedToDifferentDisplay) { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 676c6c01d349..340d5a12f92e 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -96,7 +96,6 @@ import android.util.ArraySet; import android.util.DebugUtils; import android.util.LauncherIcons; import android.util.Log; -import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.Immutable; @@ -1748,7 +1747,7 @@ public class ApplicationPackageManager extends PackageManager { final Resources r = mContext.mMainThread.getTopLevelResources( sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, - app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, + app.resourceDirs, app.sharedLibraryFiles, mContext.mPackageInfo); if (r != null) { return r; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f6b533434ac2..96d0d40f8027 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -227,6 +227,15 @@ class ContextImpl extends Context { private @NonNull Resources mResources; private @Nullable Display mDisplay; // may be null if invalid display or not initialized yet. + /** + * If set to {@code true} the resources for this context will be configured for mDisplay which + * will override the display configuration inherited from {@link #mToken} (or the global + * configuration if mToken is null). Typically set for display contexts and contexts derived + * from display contexts where changes to the activity display and the global configuration + * display should not impact their resources. + */ + private boolean mForceDisplayOverrideInResources; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mFlags; @@ -2246,8 +2255,8 @@ class ContextImpl extends Context { } private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo, - List<ResourcesLoader> resourcesLoader) { + @Nullable Integer overrideDisplayId, Configuration overrideConfig, + CompatibilityInfo compatInfo, List<ResourcesLoader> resourcesLoader) { final String[] splitResDirs; final ClassLoader classLoader; try { @@ -2261,7 +2270,7 @@ class ContextImpl extends Context { splitResDirs, pi.getOverlayDirs(), pi.getApplicationInfo().sharedLibraryFiles, - displayId, + overrideDisplayId, overrideConfig, compatInfo, classLoader, @@ -2278,8 +2287,10 @@ class ContextImpl extends Context { new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); final int displayId = getDisplayId(); + final Integer overrideDisplayId = mForceDisplayOverrideInResources + ? displayId : null; - c.setResources(createResources(mToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, overrideDisplayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo(), null)); if (c.mResources != null) { return c; @@ -2313,8 +2324,10 @@ class ContextImpl extends Context { mToken, user, flags, null, null); final int displayId = getDisplayId(); + final Integer overrideDisplayId = mForceDisplayOverrideInResources + ? displayId : null; - c.setResources(createResources(mToken, pi, null, displayId, null, + c.setResources(createResources(mToken, pi, null, overrideDisplayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo(), null)); if (c.mResources != null) { return c; @@ -2348,15 +2361,13 @@ class ContextImpl extends Context { final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, splitName, mToken, mUser, mFlags, classLoader, null); - final int displayId = getDisplayId(); - context.setResources(ResourcesManager.getInstance().getResources( mToken, mPackageInfo.getResDir(), paths, mPackageInfo.getOverlayDirs(), mPackageInfo.getApplicationInfo().sharedLibraryFiles, - displayId, + mForceDisplayOverrideInResources ? getDisplayId() : null, null, mPackageInfo.getCompatibilityInfo(), classLoader, @@ -2370,12 +2381,23 @@ class ContextImpl extends Context { throw new IllegalArgumentException("overrideConfiguration must not be null"); } + if (mForceDisplayOverrideInResources) { + // Ensure the resources display metrics are adjusted to match the display this context + // is based on. + Configuration displayAdjustedConfig = new Configuration(); + displayAdjustedConfig.setTo(mDisplay.getDisplayAdjustments().getConfiguration(), + ActivityInfo.CONFIG_WINDOW_CONFIGURATION, 1); + displayAdjustedConfig.updateFrom(overrideConfiguration); + overrideConfiguration = displayAdjustedConfig; + } + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = getDisplayId(); - - context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, + final Integer overrideDisplayId = mForceDisplayOverrideInResources + ? displayId : null; + context.setResources(createResources(mToken, mPackageInfo, mSplitName, overrideDisplayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(), mResources.getLoaders())); context.mIsUiContext = isUiContext() || isOuterUiContext(); @@ -2393,11 +2415,20 @@ class ContextImpl extends Context { final int displayId = display.getDisplayId(); + // Ensure the resources display metrics are adjusted to match the provided display. + Configuration overrideConfig = new Configuration(); + overrideConfig.setTo(display.getDisplayAdjustments().getConfiguration(), + ActivityInfo.CONFIG_WINDOW_CONFIGURATION, 1); + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, - null, getDisplayAdjustments(displayId).getCompatibilityInfo(), + overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(), mResources.getLoaders())); context.mDisplay = display; context.mIsAssociatedWithDisplay = true; + // Display contexts and any context derived from a display context should always override + // the display that would otherwise be inherited from mToken (or the global configuration if + // mToken is null). + context.mForceDisplayOverrideInResources = true; return context; } @@ -2415,8 +2446,10 @@ class ContextImpl extends Context { ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mAttributionTag, mSplitName, token, mUser, mFlags, mClassLoader, null); context.mIsUiContext = true; - context.mIsAssociatedWithDisplay = true; + // Window contexts receive configurations directly from the server and as such do not + // need to override their display in ResourcesManager. + context.mForceDisplayOverrideInResources = false; return context; } @@ -2759,6 +2792,7 @@ class ContextImpl extends Context { mDisplay = container.mDisplay; mIsAssociatedWithDisplay = container.mIsAssociatedWithDisplay; mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext; + mForceDisplayOverrideInResources = container.mForceDisplayOverrideInResources; } else { mBasePackageName = packageInfo.mPackageName; ApplicationInfo ainfo = packageInfo.getApplicationInfo(); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index aa6a08b6d2e4..202b6152d2ea 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -56,7 +56,6 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import android.view.Display; import android.view.DisplayAdjustments; import com.android.internal.util.ArrayUtils; @@ -367,7 +366,7 @@ public final class LoadedApk { mResources = ResourcesManager.getInstance().getResources(null, mResDir, splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), + null, null, getCompatibilityInfo(), getClassLoader(), mApplication == null ? null : mApplication.getResources().getLoaders()); } @@ -1231,7 +1230,7 @@ public final class LoadedApk { mResources = ResourcesManager.getInstance().getResources(null, mResDir, splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), + null, null, getCompatibilityInfo(), getClassLoader(), null); } return mResources; diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index fb2120e5a35b..887802d2660d 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -17,6 +17,8 @@ package android.app; import static android.app.ActivityThread.DEBUG_CONFIGURATION; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; import android.annotation.NonNull; import android.annotation.Nullable; @@ -61,6 +63,7 @@ import java.util.List; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Consumer; +import java.util.function.Function; /** @hide */ public class ResourcesManager { @@ -82,6 +85,12 @@ public class ResourcesManager { private final Configuration mResConfiguration = new Configuration(); /** + * The display upon which all Resources are based. Activity, window token, and display context + * resources apply their overrides to this display id. + */ + private int mResDisplayId = DEFAULT_DISPLAY; + + /** * A mapping of ResourceImpls and their configurations. These are heavy weight objects * which should be reused as much as possible. */ @@ -155,20 +164,79 @@ public class ResourcesManager { private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); /** - * Resources and base configuration override associated with an Activity. + * Class containing the base configuration override and set of resources associated with an + * Activity or {@link WindowContext}. */ private static class ActivityResources { + /** + * Override config to apply to all resources associated with the token this instance is + * based on. + * + * @see #activityResources + * @see #getResources(IBinder, String, String[], String[], String[], Integer, Configuration, + * CompatibilityInfo, ClassLoader, List) + */ + public final Configuration overrideConfig = new Configuration(); + + /** + * The display to apply to all resources associated with the token this instance is based + * on. + */ + public int overrideDisplayId; + + /** List of {@link ActivityResource} associated with the token this instance is based on. */ + public final ArrayList<ActivityResource> activityResources = new ArrayList<>(); + + public final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>(); + @UnsupportedAppUsage - private ActivityResources() { + private ActivityResources() {} + + /** Returns the number of live resource references within {@code activityResources}. */ + public int countLiveReferences() { + int count = 0; + for (int i = 0; i < activityResources.size(); i++) { + WeakReference<Resources> resources = activityResources.get(i).resources; + if (resources != null && resources.get() != null) { + count++; + } + } + return count; } + } + + /** + * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information + * about how this resource expects its configuration to differ from the token's. + * + * @see ActivityResources + */ + // TODO: Ideally this class should be called something token related, like TokenBasedResource. + private static class ActivityResource { + /** + * The override configuration applied on top of the token's override config for this + * resource. + */ public final Configuration overrideConfig = new Configuration(); - public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>(); - final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>(); + + /** + * If non-null this resource expects its configuration to override the display from the + * token's configuration. + * + * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration) + */ + @Nullable + public Integer overrideDisplayId; + + @Nullable + public WeakReference<Resources> resources; + + private ActivityResource() {} } /** - * Each Activity may has a base override configuration that is applied to each Resources object, - * which in turn may have their own override configuration specified. + * Each Activity or WindowToken may has a base override configuration that is applied to each + * Resources object, which in turn may have their own override configuration specified. */ @UnsupportedAppUsage private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences = @@ -241,8 +309,7 @@ public class ResourcesManager { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public DisplayMetrics getDisplayMetrics() { - return getDisplayMetrics(Display.DEFAULT_DISPLAY, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + return getDisplayMetrics(mResDisplayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); } /** @@ -260,8 +327,8 @@ public class ResourcesManager { return dm; } - private static void applyNonDefaultDisplayMetricsToConfiguration( - @NonNull DisplayMetrics dm, @NonNull Configuration config) { + private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm, + @NonNull Configuration config) { config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; config.densityDpi = dm.densityDpi; config.screenWidthDp = (int) (dm.widthPixels / dm.density); @@ -502,7 +569,7 @@ public class ResourcesManager { int references = countLiveReferences(mResourceReferences); for (ActivityResources activityResources : mActivityResourceReferences.values()) { - references += countLiveReferences(activityResources.activityResources); + references += activityResources.countLiveReferences(); } pw.println(references); @@ -511,38 +578,36 @@ public class ResourcesManager { } } - private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { + private Configuration generateConfig(@NonNull ResourcesKey key) { Configuration config; - final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); final boolean hasOverrideConfig = key.hasOverrideConfiguration(); - if (!isDefaultDisplay || hasOverrideConfig) { + if (hasOverrideConfig) { config = new Configuration(getConfiguration()); - if (!isDefaultDisplay) { - applyNonDefaultDisplayMetricsToConfiguration(dm, config); - } - if (hasOverrideConfig) { - config.updateFrom(key.mOverrideConfiguration); - if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); - } + config.updateFrom(key.mOverrideConfiguration); + if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); } else { config = getConfiguration(); } return config; } + private int generateDisplayId(@NonNull ResourcesKey key) { + return key.mDisplayId != INVALID_DISPLAY ? key.mDisplayId : mResDisplayId; + } + private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { - final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); - daj.setCompatibilityInfo(key.mCompatInfo); - final AssetManager assets = createAssetManager(key, apkSupplier); if (assets == null) { return null; } - final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj); - final Configuration config = generateConfig(key, dm); - final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); + final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); + daj.setCompatibilityInfo(key.mCompatInfo); + + final Configuration config = generateConfig(key); + final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj); + final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj); if (DEBUG) { Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); @@ -652,8 +717,8 @@ public class ResourcesManager { final int size = activityResources.activityResources.size(); for (int index = 0; index < size; index++) { - WeakReference<Resources> ref = activityResources.activityResources.get(index); - Resources resources = ref.get(); + ActivityResource activityResource = activityResources.activityResources.get(index); + Resources resources = activityResource.resources.get(); ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked( resources.getImpl()); @@ -667,20 +732,28 @@ public class ResourcesManager { return null; } - private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken, + @NonNull + private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken, + @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) { final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( activityToken); cleanupReferences(activityResources.activityResources, - activityResources.activityResourcesQueue); + activityResources.activityResourcesQueue, + (r) -> r.resources); Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); resources.setImpl(impl); resources.setCallbacks(mUpdateCallbacks); - activityResources.activityResources.add( - new WeakReference<>(resources, activityResources.activityResourcesQueue)); + + ActivityResource activityResource = new ActivityResource(); + activityResource.resources = new WeakReference<>(resources, + activityResources.activityResourcesQueue); + activityResource.overrideConfig.setTo(initialOverrideConfig); + activityResource.overrideDisplayId = overrideDisplayId; + activityResources.activityResources.add(activityResource); if (DEBUG) { Slog.d(TAG, "- creating new ref=" + resources); Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); @@ -706,7 +779,7 @@ public class ResourcesManager { /** * Creates base resources for a binder token. Calls to - * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, + * {@link #getResources(IBinder, String, String[], String[], String[], Integer, Configuration, * CompatibilityInfo, ClassLoader, List)} with the same binder token will have their override * configurations merged with the one specified here. * @@ -743,7 +816,7 @@ public class ResourcesManager { overlayDirs, libDirs, displayId, - overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy + overrideConfig, compatInfo, loaders == null ? null : loaders.toArray(new ResourcesLoader[0])); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); @@ -759,10 +832,7 @@ public class ResourcesManager { } // Update any existing Activity Resources references. - updateResourcesForActivity(token, overrideConfig, displayId, - false /* movedToDifferentDisplay */); - - rebaseKeyForActivity(token, key); + updateResourcesForActivity(token, overrideConfig, displayId); synchronized (this) { Resources resources = findResourcesForActivityLocked(token, key, @@ -773,7 +843,9 @@ public class ResourcesManager { } // Now request an actual Resources object. - return createResources(token, key, classLoader, /* apkSupplier */ null); + return createResourcesForActivity(token, key, + /* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null, + classLoader, /* apkSupplier */ null); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -781,20 +853,65 @@ public class ResourcesManager { /** * Rebases a key's override config on top of the Activity's base override. + * + * @param activityToken the token the supplied {@code key} is derived from. + * @param key the key to rebase + * @param overridesActivityDisplay whether this key is overriding the display from the token */ - private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) { + private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key, + boolean overridesActivityDisplay) { synchronized (this) { final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(activityToken); - // Rebase the key's override config on top of the Activity's base override. - if (key.hasOverrideConfiguration() - && !activityResources.overrideConfig.equals(Configuration.EMPTY)) { - final Configuration temp = new Configuration(activityResources.overrideConfig); - temp.updateFrom(key.mOverrideConfiguration); - key.mOverrideConfiguration.setTo(temp); + if (key.mDisplayId == INVALID_DISPLAY) { + key.mDisplayId = activityResources.overrideDisplayId; + } + + Configuration config; + if (key.hasOverrideConfiguration()) { + config = new Configuration(activityResources.overrideConfig); + config.updateFrom(key.mOverrideConfiguration); + } else { + config = activityResources.overrideConfig; + } + + if (overridesActivityDisplay + && key.mOverrideConfiguration.windowConfiguration.getAppBounds() == null) { + if (!key.hasOverrideConfiguration()) { + // Make a copy to handle the case where the override config is set to defaults. + config = new Configuration(config); + } + + // If this key is overriding the display from the token and the key's + // window config app bounds is null we need to explicitly override this to + // ensure the display adjustments are as expected. + config.windowConfiguration.setAppBounds(null); } + + key.mOverrideConfiguration.setTo(config); + } + } + + /** + * Rebases a key's override config with display metrics of the {@code overrideDisplay} paired + * with the {code displayAdjustments}. + * + * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration) + */ + private void rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay) { + final Configuration temp = new Configuration(); + + DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); + daj.setCompatibilityInfo(key.mCompatInfo); + + final DisplayMetrics dm = getDisplayMetrics(overrideDisplay, daj); + applyDisplayMetricsToConfiguration(dm, temp); + + if (key.hasOverrideConfiguration()) { + temp.updateFrom(key.mOverrideConfiguration); } + key.mOverrideConfiguration.setTo(temp); } /** @@ -802,18 +919,28 @@ public class ResourcesManager { */ private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references, ReferenceQueue<T> referenceQueue) { - Reference<? extends T> enduedRef = referenceQueue.poll(); - if (enduedRef == null) { + cleanupReferences(references, referenceQueue, Function.identity()); + } + + /** + * Check WeakReferences and remove any dead references so they don't pile up. + */ + private static <C, T> void cleanupReferences(ArrayList<C> referenceContainers, + ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction) { + Reference<? extends T> enqueuedRef = referenceQueue.poll(); + if (enqueuedRef == null) { return; } final HashSet<Reference<? extends T>> deadReferences = new HashSet<>(); - for (; enduedRef != null; enduedRef = referenceQueue.poll()) { - deadReferences.add(enduedRef); + for (; enqueuedRef != null; enqueuedRef = referenceQueue.poll()) { + deadReferences.add(enqueuedRef); } - ArrayUtils.unstableRemoveIf(references, - (ref) -> ref == null || deadReferences.contains(ref)); + ArrayUtils.unstableRemoveIf(referenceContainers, (refContainer) -> { + WeakReference<T> ref = unwrappingFunction.apply(refContainer); + return ref == null || deadReferences.contains(ref); + }); } /** @@ -849,23 +976,21 @@ public class ResourcesManager { /** * Creates a Resources object set with a ResourcesImpl object matching the given key. * - * @param activityToken The Activity this Resources object should be associated with. * @param key The key describing the parameters of the ResourcesImpl object. * @param classLoader The classloader to use for the Resources object. * If null, {@link ClassLoader#getSystemClassLoader()} is used. - * @param apkSupplier The apk assets supplier to use when creating a new ResourcesImpl object. * @return A Resources object that gets updated when * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)} * is called. */ - private @Nullable Resources createResources(@Nullable IBinder activityToken, - @NonNull ResourcesKey key, @NonNull ClassLoader classLoader, + @Nullable + private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier) { synchronized (this) { if (DEBUG) { Throwable here = new Throwable(); here.fillInStackTrace(); - Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); + Slog.w(TAG, "!! Create resources for key=" + key, here); } ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier); @@ -873,12 +998,29 @@ public class ResourcesManager { return null; } - if (activityToken != null) { - return createResourcesForActivityLocked(activityToken, classLoader, - resourcesImpl, key.mCompatInfo); - } else { - return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); + return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); + } + } + + @Nullable + private Resources createResourcesForActivity(@NonNull IBinder activityToken, + @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig, + @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, + @Nullable ApkAssetsSupplier apkSupplier) { + synchronized (this) { + if (DEBUG) { + Throwable here = new Throwable(); + here.fillInStackTrace(); + Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); + } + + ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier); + if (resourcesImpl == null) { + return null; } + + return createResourcesForActivityLocked(activityToken, initialOverrideConfig, + overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo); } } @@ -899,7 +1041,10 @@ public class ResourcesManager { * @param splitResDirs An array of split resource paths. Can be null. * @param overlayDirs An array of overlay paths. Can be null. * @param libDirs An array of resource library paths. Can be null. - * @param displayId The ID of the display for which to create the resources. + * @param overrideDisplayId The ID of the display for which the returned Resources should be + * based. This will cause display-based configuration properties to override those of the base + * Resources for the {@code activityToken}, or the global configuration if {@code activityToken} + * is null. * @param overrideConfig The configuration to apply on top of the base configuration. Can be * null. Mostly used with Activities that are in multi-window which may override width and * height properties from the base config. @@ -909,13 +1054,14 @@ public class ResourcesManager { * {@link ClassLoader#getSystemClassLoader()} is used. * @return a Resources object from which to access resources. */ - public @Nullable Resources getResources( + @Nullable + public Resources getResources( @Nullable IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] overlayDirs, @Nullable String[] libDirs, - int displayId, + @Nullable Integer overrideDisplayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @@ -927,20 +1073,30 @@ public class ResourcesManager { splitResDirs, overlayDirs, libDirs, - displayId, - overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy + overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY, + overrideConfig, compatInfo, loaders == null ? null : loaders.toArray(new ResourcesLoader[0])); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); - if (activityToken != null) { - rebaseKeyForActivity(activityToken, key); - } - // Preload the ApkAssets required by the key to prevent performing heavy I/O while the // ResourcesManager lock is held. final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key); - return createResources(activityToken, key, classLoader, assetsSupplier); + + if (overrideDisplayId != null) { + rebaseKeyForDisplay(key, overrideDisplayId); + } + + Resources resources; + if (activityToken != null) { + Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration); + rebaseKeyForActivity(activityToken, key, overrideDisplayId != null); + resources = createResourcesForActivity(activityToken, key, initialOverrideConfig, + overrideDisplayId, classLoader, assetsSupplier); + } else { + resources = createResources(key, classLoader, assetsSupplier); + } + return resources; } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -949,24 +1105,26 @@ public class ResourcesManager { /** * Updates an Activity's Resources object with overrideConfig. The Resources object * that was previously returned by {@link #getResources(IBinder, String, String[], String[], - * String[], int, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and will - * have the updated configuration. + * String[], Integer, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and + * will have the updated configuration. * * @param activityToken The Activity token. * @param overrideConfig The configuration override to update. * @param displayId Id of the display where activity currently resides. - * @param movedToDifferentDisplay Indicates if the activity was moved to different display. */ public void updateResourcesForActivity(@NonNull IBinder activityToken, - @Nullable Configuration overrideConfig, int displayId, - boolean movedToDifferentDisplay) { + @Nullable Configuration overrideConfig, int displayId) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#updateResourcesForActivity"); + if (displayId == INVALID_DISPLAY) { + throw new IllegalArgumentException("displayId can not be INVALID_DISPLAY"); + } synchronized (this) { final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(activityToken); + boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId; if (Objects.equals(activityResources.overrideConfig, overrideConfig) && !movedToDifferentDisplay) { // They are the same and no change of display id, no work to do. @@ -983,6 +1141,8 @@ public class ResourcesManager { } else { activityResources.overrideConfig.unset(); } + // Update the Activity's override display id. + activityResources.overrideDisplayId = displayId; if (DEBUG) { Throwable here = new Throwable(); @@ -1000,24 +1160,26 @@ public class ResourcesManager { // Rebase each Resources associated with this Activity. final int refCount = activityResources.activityResources.size(); for (int i = 0; i < refCount; i++) { - final WeakReference<Resources> weakResRef = + final ActivityResource activityResource = activityResources.activityResources.get(i); - final Resources resources = weakResRef.get(); + final Resources resources = activityResource.resources.get(); if (resources == null) { continue; } - final ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig, + final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource, overrideConfig, displayId); - if (newKey != null) { - final ResourcesImpl resourcesImpl = - findOrCreateResourcesImplForKeyLocked(newKey); - if (resourcesImpl != null && resourcesImpl != resources.getImpl()) { - // Set the ResourcesImpl, updating it for all users of this Resources - // object. - resources.setImpl(resourcesImpl); - } + if (newKey == null) { + continue; + } + + final ResourcesImpl resourcesImpl = + findOrCreateResourcesImplForKeyLocked(newKey); + if (resourcesImpl != null && resourcesImpl != resources.getImpl()) { + // Set the ResourcesImpl, updating it for all users of this Resources + // object. + resources.setImpl(resourcesImpl); } } } @@ -1031,9 +1193,13 @@ public class ResourcesManager { * that an Activity's Resources should be set to. */ @Nullable - private ResourcesKey rebaseActivityOverrideConfig(@NonNull Resources resources, - @NonNull Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig, - int displayId) { + private ResourcesKey rebaseActivityOverrideConfig(@NonNull ActivityResource activityResource, + @Nullable Configuration newOverrideConfig, int displayId) { + final Resources resources = activityResource.resources.get(); + if (resources == null) { + return null; + } + // Extract the ResourcesKey that was last used to create the Resources for this // activity. final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); @@ -1049,16 +1215,33 @@ public class ResourcesManager { rebasedOverrideConfig.setTo(newOverrideConfig); } - final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY); - if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) { - // Generate a delta between the old base Activity override configuration and - // the actual final override configuration that was used to figure out the - // real delta this Resources object wanted. - Configuration overrideOverrideConfig = Configuration.generateDelta( - oldOverrideConfig, oldKey.mOverrideConfiguration); - rebasedOverrideConfig.updateFrom(overrideOverrideConfig); + final Integer overrideDisplayId = activityResource.overrideDisplayId; + if (overrideDisplayId != null) { + DisplayAdjustments displayAdjustments = new DisplayAdjustments(rebasedOverrideConfig); + displayAdjustments.getConfiguration().setTo(activityResource.overrideConfig); + displayAdjustments.setCompatibilityInfo(oldKey.mCompatInfo); + + DisplayMetrics dm = getDisplayMetrics(overrideDisplayId, displayAdjustments); + applyDisplayMetricsToConfiguration(dm, rebasedOverrideConfig); + } + + final boolean hasOverrideConfig = + !activityResource.overrideConfig.equals(Configuration.EMPTY); + if (hasOverrideConfig) { + rebasedOverrideConfig.updateFrom(activityResource.overrideConfig); } + if (activityResource.overrideDisplayId != null + && activityResource.overrideConfig.windowConfiguration.getAppBounds() == null) { + // If this activity resource is overriding the display from the token and the key's + // window config app bounds is null we need to explicitly override this to + // ensure the display adjustments are as expected. + rebasedOverrideConfig.windowConfiguration.setAppBounds(null); + } + + // Ensure the new key keeps the expected override display instead of the new token display. + displayId = overrideDisplayId != null ? overrideDisplayId : displayId; + // Create the new ResourcesKey with the rebased override config. final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs, @@ -1090,12 +1273,11 @@ public class ResourcesManager { + mResConfiguration.seq + ", newSeq=" + config.seq); return false; } - int changes = mResConfiguration.updateFrom(config); + // Things might have changed in display manager, so clear the cached displays. mAdjustedDisplays.clear(); - DisplayMetrics defaultDisplayMetrics = getDisplayMetrics(); - + int changes = mResConfiguration.updateFrom(config); if (compat != null && (mResCompatibilityInfo == null || !mResCompatibilityInfo.equals(compat))) { mResCompatibilityInfo = compat; @@ -1104,10 +1286,10 @@ public class ResourcesManager { | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } - Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); + DisplayMetrics displayMetrics = getDisplayMetrics(); + Resources.updateSystemConfiguration(config, displayMetrics, compat); ApplicationPackageManager.configurationChanged(); - //Slog.i(TAG, "Configuration changed in " + currentPackageName()); Configuration tmpConfig = new Configuration(); @@ -1137,11 +1319,7 @@ public class ResourcesManager { } tmpConfig.setTo(config); - - // Apply the override configuration before setting the display adjustments to ensure that - // the process config does not override activity display adjustments. - final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); - if (hasOverrideConfiguration) { + if (key.hasOverrideConfiguration()) { tmpConfig.updateFrom(key.mOverrideConfiguration); } @@ -1153,22 +1331,8 @@ public class ResourcesManager { daj = new DisplayAdjustments(daj); daj.setCompatibilityInfo(compat); } - - final int displayId = key.mDisplayId; - if (displayId == Display.DEFAULT_DISPLAY) { - daj.setConfiguration(tmpConfig); - } - DisplayMetrics dm = getDisplayMetrics(displayId, daj); - if (displayId != Display.DEFAULT_DISPLAY) { - applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); - - // Re-apply the override configuration to ensure that configuration contexts based on - // a display context (ex: createDisplayContext().createConfigurationContext()) have the - // correct override. - if (hasOverrideConfiguration) { - tmpConfig.updateFrom(key.mOverrideConfiguration); - } - } + daj.setConfiguration(tmpConfig); + DisplayMetrics dm = getDisplayMetrics(generateDisplayId(key), daj); resourcesImpl.updateConfiguration(tmpConfig, dm, compat); } @@ -1305,8 +1469,10 @@ public class ResourcesManager { for (ActivityResources activityResources : mActivityResourceReferences.values()) { final int resCount = activityResources.activityResources.size(); for (int i = 0; i < resCount; i++) { - final WeakReference<Resources> ref = activityResources.activityResources.get(i); - final Resources r = ref != null ? ref.get() : null; + final ActivityResource activityResource = + activityResources.activityResources.get(i); + final Resources r = activityResource != null + ? activityResource.resources.get() : null; if (r != null) { final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); if (key != null) { @@ -1338,10 +1504,16 @@ public class ResourcesManager { if (tokenResources == null) { return false; } - final ArrayList<WeakReference<Resources>> resourcesRefs = - tokenResources.activityResources; + final ArrayList<ActivityResource> resourcesRefs = tokenResources.activityResources; for (int i = resourcesRefs.size() - 1; i >= 0; i--) { - final Resources res = resourcesRefs.get(i).get(); + final ActivityResource activityResource = resourcesRefs.get(i); + if (activityResource.overrideDisplayId != null) { + // This resource overrides the display of the token so we should not be + // modifying its display adjustments here. + continue; + } + + final Resources res = activityResource.resources.get(); if (res != null) { res.overrideDisplayAdjustments(override); handled = true; diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java index b5d103959818..9092ef36deb6 100644 --- a/core/java/android/app/WindowTokenClient.java +++ b/core/java/android/app/WindowTokenClient.java @@ -71,8 +71,7 @@ public class WindowTokenClient extends IWindowToken.Stub { final boolean configChanged = config.diff(newConfig) != 0; if (displayChanged || configChanged) { // TODO(ag/9789103): update resource manager logic to track non-activity tokens - mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId, - displayChanged); + mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); } if (displayChanged) { context.updateDisplay(newDisplayId); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 200604801b97..0f1f276ce0f9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -88,7 +88,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureVerifier; -import android.view.Display; import android.view.Gravity; import com.android.internal.R; @@ -8536,7 +8535,7 @@ public class PackageParser { null, androidAppInfo.resourceDirs, androidAppInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, + null, null, systemResources.getCompatibilityInfo(), systemResources.getClassLoader(), diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java index 9da0f20d1006..fcb80aa3e58d 100644 --- a/core/java/android/content/res/ResourcesKey.java +++ b/core/java/android/content/res/ResourcesKey.java @@ -16,6 +16,8 @@ package android.content.res; +import static android.os.Build.VERSION_CODES.O; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -41,8 +43,17 @@ public final class ResourcesKey { @Nullable public final String[] mLibDirs; - public final int mDisplayId; - + /** + * The display ID that overrides the global resources display to produce the Resources display. + * If set to something other than {@link android.view.Display#INVALID_DISPLAY} this will + * override the global resources display for this key. + */ + @UnsupportedAppUsage(maxTargetSdk = O) + public int mDisplayId; + + /** + * The configuration applied to the global configuration to produce the Resources configuration. + */ @NonNull public final Configuration mOverrideConfiguration; @@ -58,7 +69,7 @@ public final class ResourcesKey { @Nullable String[] splitResDirs, @Nullable String[] overlayDirs, @Nullable String[] libDirs, - int displayId, + int overrideDisplayId, @Nullable Configuration overrideConfig, @Nullable CompatibilityInfo compatInfo, @Nullable ResourcesLoader[] loader) { @@ -67,7 +78,7 @@ public final class ResourcesKey { mOverlayDirs = overlayDirs; mLibDirs = libDirs; mLoaders = (loader != null && loader.length == 0) ? null : loader; - mDisplayId = displayId; + mDisplayId = overrideDisplayId; mOverrideConfiguration = new Configuration(overrideConfig != null ? overrideConfig : Configuration.EMPTY); mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; @@ -77,7 +88,7 @@ public final class ResourcesKey { hash = 31 * hash + Arrays.hashCode(mSplitResDirs); hash = 31 * hash + Arrays.hashCode(mOverlayDirs); hash = 31 * hash + Arrays.hashCode(mLibDirs); - hash = 31 * hash + mDisplayId; + hash = 31 * hash + Objects.hashCode(mDisplayId); hash = 31 * hash + Objects.hashCode(mOverrideConfiguration); hash = 31 * hash + Objects.hashCode(mCompatInfo); hash = 31 * hash + Arrays.hashCode(mLoaders); diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 000e870369db..0a751dd7c66b 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -408,6 +408,9 @@ public class ActivityThreadTest { int originalVirtualDisplayOrientation = virtualDisplayContext.getResources() .getConfiguration().orientation; + + // Perform global config change and verify there is no config change in derived display + // context. Configuration newAppConfig = new Configuration(originalAppConfig); newAppConfig.seq++; newAppConfig.orientation = newAppConfig.orientation == ORIENTATION_PORTRAIT @@ -417,7 +420,7 @@ public class ActivityThreadTest { activityThread.handleConfigurationChanged(newAppConfig); try { - assertEquals("Virtual display orientation should not change when process" + assertEquals("Virtual display orientation must not change when process" + " configuration orientation changes.", originalVirtualDisplayOrientation, virtualDisplayContext.getResources().getConfiguration().orientation); @@ -438,6 +441,50 @@ public class ActivityThreadTest { } @Test + public void testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation() { + final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); + final ActivityThread activityThread = activity.getActivityThread(); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + Configuration originalActivityConfig = + new Configuration(activity.getResources().getConfiguration()); + DisplayManager dm = activity.getSystemService(DisplayManager.class); + + int virtualDisplayWidth; + int virtualDisplayHeight; + if (originalActivityConfig.orientation == ORIENTATION_PORTRAIT) { + virtualDisplayWidth = 100; + virtualDisplayHeight = 200; + } else { + virtualDisplayWidth = 200; + virtualDisplayHeight = 100; + } + Display virtualDisplay = dm.createVirtualDisplay("virtual-display", + virtualDisplayWidth, virtualDisplayHeight, 200, null, 0).getDisplay(); + Context virtualDisplayContext = activity.createDisplayContext(virtualDisplay); + int originalVirtualDisplayOrientation = virtualDisplayContext.getResources() + .getConfiguration().orientation; + + // Perform activity config change and verify there is no config change in derived + // display context. + Configuration newActivityConfig = new Configuration(originalActivityConfig); + newActivityConfig.seq++; + newActivityConfig.orientation = newActivityConfig.orientation == ORIENTATION_PORTRAIT + ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; + + activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), + newActivityConfig); + activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), + newActivityConfig, INVALID_DISPLAY); + + assertEquals("Virtual display orientation must not change when activity" + + " configuration orientation changes.", + originalVirtualDisplayOrientation, + virtualDisplayContext.getResources().getConfiguration().orientation); + }); + } + + @Test public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() { final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index efcd458e19cc..45adf833de97 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -29,31 +29,50 @@ import androidx.test.filters.SmallTest; import junit.framework.TestCase; +import java.util.HashMap; +import java.util.Map; + public class ResourcesManagerTest extends TestCase { + private static final int SECONDARY_DISPLAY_ID = 1; private static final String APP_ONE_RES_DIR = "app_one.apk"; private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk"; private static final String APP_TWO_RES_DIR = "app_two.apk"; private static final String LIB_RES_DIR = "lib.apk"; private ResourcesManager mResourcesManager; - private DisplayMetrics mDisplayMetrics; + private Map<Integer, DisplayMetrics> mDisplayMetricsMap; @Override protected void setUp() throws Exception { super.setUp(); - mDisplayMetrics = new DisplayMetrics(); - mDisplayMetrics.setToDefaults(); + mDisplayMetricsMap = new HashMap<>(); + + DisplayMetrics defaultDisplayMetrics = new DisplayMetrics(); + defaultDisplayMetrics.setToDefaults(); // Override defaults (which take device specific properties). - mDisplayMetrics.density = 1.0f; - mDisplayMetrics.densityDpi = DisplayMetrics.DENSITY_DEFAULT; - mDisplayMetrics.xdpi = DisplayMetrics.DENSITY_DEFAULT; - mDisplayMetrics.ydpi = DisplayMetrics.DENSITY_DEFAULT; - mDisplayMetrics.noncompatDensity = mDisplayMetrics.density; - mDisplayMetrics.noncompatDensityDpi = mDisplayMetrics.densityDpi; - mDisplayMetrics.noncompatXdpi = DisplayMetrics.DENSITY_DEFAULT; - mDisplayMetrics.noncompatYdpi = DisplayMetrics.DENSITY_DEFAULT; + defaultDisplayMetrics.density = 1.0f; + defaultDisplayMetrics.densityDpi = DisplayMetrics.DENSITY_DEFAULT; + defaultDisplayMetrics.xdpi = DisplayMetrics.DENSITY_DEFAULT; + defaultDisplayMetrics.ydpi = DisplayMetrics.DENSITY_DEFAULT; + defaultDisplayMetrics.widthPixels = 1440; + defaultDisplayMetrics.heightPixels = 2960; + defaultDisplayMetrics.noncompatDensity = defaultDisplayMetrics.density; + defaultDisplayMetrics.noncompatDensityDpi = defaultDisplayMetrics.densityDpi; + defaultDisplayMetrics.noncompatXdpi = DisplayMetrics.DENSITY_DEFAULT; + defaultDisplayMetrics.noncompatYdpi = DisplayMetrics.DENSITY_DEFAULT; + defaultDisplayMetrics.noncompatWidthPixels = defaultDisplayMetrics.widthPixels; + defaultDisplayMetrics.noncompatHeightPixels = defaultDisplayMetrics.heightPixels; + mDisplayMetricsMap.put(Display.DEFAULT_DISPLAY, defaultDisplayMetrics); + + DisplayMetrics secondaryDisplayMetrics = new DisplayMetrics(); + secondaryDisplayMetrics.setTo(defaultDisplayMetrics); + secondaryDisplayMetrics.widthPixels = 50; + secondaryDisplayMetrics.heightPixels = 100; + secondaryDisplayMetrics.noncompatWidthPixels = secondaryDisplayMetrics.widthPixels; + secondaryDisplayMetrics.noncompatHeightPixels = secondaryDisplayMetrics.heightPixels; + mDisplayMetricsMap.put(SECONDARY_DISPLAY_ID, secondaryDisplayMetrics); mResourcesManager = new ResourcesManager() { @Override @@ -63,7 +82,7 @@ public class ResourcesManagerTest extends TestCase { @Override protected DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments daj) { - return mDisplayMetrics; + return mDisplayMetricsMap.get(displayId); } }; } @@ -71,12 +90,12 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testMultipleCallsWithIdenticalParametersCacheReference() { Resources resources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Resources newResources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertSame(resources, newResources); @@ -85,14 +104,14 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() { Resources resources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Configuration overrideConfig = new Configuration(); overrideConfig.smallestScreenWidthDp = 200; Resources newResources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, overrideConfig, + null, APP_ONE_RES_DIR, null, null, null, null, overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertNotSame(resources, newResources); @@ -101,13 +120,13 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testAddingASplitCreatesANewImpl() { Resources resources1 = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null, - Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,null, + null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); @@ -118,12 +137,12 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testUpdateConfigurationUpdatesAllAssetManagers() { Resources resources1 = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( - null, APP_TWO_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + null, APP_TWO_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); @@ -131,7 +150,7 @@ public class ResourcesManagerTest extends TestCase { final Configuration overrideConfig = new Configuration(); overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE; Resources resources3 = mResourcesManager.getResources( - activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, + activity, APP_ONE_RES_DIR, null, null, null, null, overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources3); @@ -152,7 +171,7 @@ public class ResourcesManagerTest extends TestCase { final Configuration expectedConfig = new Configuration(); expectedConfig.setToDefaults(); expectedConfig.setLocales(LocaleList.getAdjustedDefault()); - expectedConfig.densityDpi = mDisplayMetrics.densityDpi; + expectedConfig.densityDpi = mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).densityDpi; expectedConfig.orientation = Configuration.ORIENTATION_LANDSCAPE; assertEquals(expectedConfig, resources1.getConfiguration()); @@ -164,13 +183,13 @@ public class ResourcesManagerTest extends TestCase { public void testTwoActivitiesWithIdenticalParametersShareImpl() { Binder activity1 = new Binder(); Resources resources1 = mResourcesManager.getResources( - activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + activity1, APP_ONE_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Binder activity2 = new Binder(); Resources resources2 = mResourcesManager.getResources( - activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + activity2, APP_ONE_RES_DIR, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); @@ -201,7 +220,7 @@ public class ResourcesManagerTest extends TestCase { final Configuration overrideConfig = new Configuration(); overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE; mResourcesManager.updateResourcesForActivity(activity1, overrideConfig, - Display.DEFAULT_DISPLAY, false /* movedToDifferentDisplay */); + Display.DEFAULT_DISPLAY); assertSame(resources1, theme.getResources()); // Make sure we can still access the data. @@ -226,7 +245,7 @@ public class ResourcesManagerTest extends TestCase { Configuration config2 = new Configuration(); config2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES; Resources resources2 = mResourcesManager.getResources( - activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config2, + activity1, APP_ONE_RES_DIR, null, null, null, null, config2, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); @@ -250,8 +269,7 @@ public class ResourcesManagerTest extends TestCase { // Now update the Activity base override, and both resources should update. config1.orientation = Configuration.ORIENTATION_LANDSCAPE; - mResourcesManager.updateResourcesForActivity(activity1, config1, Display.DEFAULT_DISPLAY, - false /* movedToDifferentDisplay */); + mResourcesManager.updateResourcesForActivity(activity1, config1, Display.DEFAULT_DISPLAY); expectedConfig1.orientation = Configuration.ORIENTATION_LANDSCAPE; assertEquals(expectedConfig1, resources1.getConfiguration()); @@ -290,4 +308,41 @@ public class ResourcesManagerTest extends TestCase { assertEquals(originalOverrideDensity, resources.getDisplayAdjustments().getConfiguration().densityDpi); } + + @SmallTest + public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() { + Binder activity = new Binder(); + + // Create a base token resources that are based on the default display. + Resources activityResources = mResourcesManager.createBaseTokenResources( + activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + // Create another resources that explicitly override the display of the base token above + // and set it to DEFAULT_DISPLAY. + Resources defaultDisplayResources = mResourcesManager.getResources( + activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + + assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, + activityResources.getDisplayMetrics().widthPixels); + assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).heightPixels, + activityResources.getDisplayMetrics().heightPixels); + assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, + defaultDisplayResources.getDisplayMetrics().widthPixels); + assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, + defaultDisplayResources.getDisplayMetrics().widthPixels); + + // Now change the display of the activity and ensure the activity's display metrics match + // the new display, but the other resources remain based on the default display. + mResourcesManager.updateResourcesForActivity(activity, null, SECONDARY_DISPLAY_ID); + + assertEquals(mDisplayMetricsMap.get(SECONDARY_DISPLAY_ID).widthPixels, + activityResources.getDisplayMetrics().widthPixels); + assertEquals(mDisplayMetricsMap.get(SECONDARY_DISPLAY_ID).heightPixels, + activityResources.getDisplayMetrics().heightPixels); + assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, + defaultDisplayResources.getDisplayMetrics().widthPixels); + assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, + defaultDisplayResources.getDisplayMetrics().widthPixels); + } } |