Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6bb2b9c7 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Make process level rotated config and display metrics consistent

The application may get Resources instance from Resources.getSystem()
and context.getApplicationContext().getResources(). Since fixed
rotation is introduced that allows an activity to start in a different
rotation than the current display, when using getConfiguration() and
getDisplayMetrics() of these Resources instances, the orientation
and metrics need to be the same as current display is rotated.
Otherwise the app may show unexpected UI layout.

Although it is not recommended to use global resources/config for
activity. One of the goal of fixed rotation transform is to simulate
the app is started in a rotated environment, so this CL makes the
configuration and display metrics of system resources are consistent
with application and activity for compatibility.

About WindowProcessController and ActivityStackSupervisor:
 The process configuration passed to LaunchActivityItem may be
 associated from activity. if the sequence number of configuration
 is overridden by activity, the configuration may be ignored when
 launching the activity because the sequence number isn't larger
 than the previous process configuration. Although there will be a
 ConfigurationChangeItem later to update correct state, the app may
 get the intermediate state with old configuration and metrics.

About ResourcesManager and DisplayAdjustments:
 There are 2 new fields appWidth and appHeight added to
 DisplayAdjustments#FixedRotationAdjustments because the display
 metrics from Resources.getSystem() is independent from activity
 configuration. Only window manager knows the rotated size, so
 the values need to send to client and then ResourcesManager takes
 the adjustment to change the global display metrics.

About WindowToken:
 When fixed rotation is applied on the token, send the
 FixedRotationAdjustmentsItem first so the later configuration
 change can pick the adjustment at ActivityThread. And because the
 registration of activity configuration only occurs on add/remove
 activity, if it is only switching to another existing activity in
 different orientation, the process configuration still needs to
 be updated.

About ActivityThread:
 The code flow for a rotated activity (DA = display adjustments):
 - Launch new activity
    handleLaunchActivity: override app DA
     handleConfigurationChanged: adjust global display metrics by DA
     performLaunchActivity
      createBaseContextForActivity: override activity DA
 - Resume existing activity
    handleFixedRotationAdjustments: override app and activity DA
    handleConfigurationChanged: adjust global display metrics by DA
    handleResumeActivity

Also some minor corrections:
- Fix wrong display metrics adjustment that xdpi and ydpi should
  not be swapped because they are physical attributes.

Bug: 167564038
Test: atest DisplayAdjustmentsTests
      AppConfigurationTests#testRotatedInfoWithFixedRotationTransform
      WindowProcessControllerTests#testProcessLevelConfiguration
      DisplayContentTests#testApplyTopFixedRotationTransform

Change-Id: I60bedc7e09f54683d5e857ccc51402d5d144cd9e
Merged-In: I60bedc7e09f54683d5e857ccc51402d5d144cd9e
parent 29bedbcd
Loading
Loading
Loading
Loading
+51 −48
Original line number Diff line number Diff line
@@ -3250,12 +3250,6 @@ public final class ActivityThread extends ClientTransactionHandler {
        sendMessage(H.CLEAN_UP_CONTEXT, cci);
    }

    @Override
    public void handleFixedRotationAdjustments(@NonNull IBinder token,
            @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
        handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */);
    }

    /**
     * Applies the rotation adjustments to override display information in resources belong to the
     * provided token. If the token is activity token, the adjustments also apply to application
@@ -3265,51 +3259,39 @@ public final class ActivityThread extends ClientTransactionHandler {
     * @param fixedRotationAdjustments The information to override the display adjustments of
     *                                 corresponding resources. If it is null, the exiting override
     *                                 will be cleared.
     * @param overrideConfig The override configuration of activity. It is used to override
     *                       application configuration. If it is non-null, it means the token is
     *                       confirmed as activity token. Especially when launching new activity,
     *                       {@link #mActivities} hasn't put the new token.
     */
    private void handleFixedRotationAdjustments(@NonNull IBinder token,
            @Nullable FixedRotationAdjustments fixedRotationAdjustments,
            @Nullable Configuration overrideConfig) {
        // The element of application configuration override is set only if the application
        // adjustments are needed, because activity already has its own override configuration.
        final Configuration[] appConfigOverride;
        final Consumer<DisplayAdjustments> override;
        if (fixedRotationAdjustments != null) {
            appConfigOverride = new Configuration[1];
            override = displayAdjustments -> {
                displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
                if (appConfigOverride[0] != null) {
                    displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]);
                }
            };
        } else {
            appConfigOverride = null;
            override = null;
        }
    @Override
    public void handleFixedRotationAdjustments(@NonNull IBinder token,
            @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
        final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
                ? displayAdjustments -> displayAdjustments
                        .setFixedRotationAdjustments(fixedRotationAdjustments)
                : null;
        if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
            // No resources are associated with the token.
            return;
        }
        if (overrideConfig == null) {
            final ActivityClientRecord r = mActivities.get(token);
            if (r == null) {
                // It is not an activity token. Nothing to do for application.
        if (mActivities.get(token) == null) {
            // Nothing to do for application if it is not an activity token.
            return;
        }
            overrideConfig = r.overrideConfig;
        }
        if (appConfigOverride != null) {
            appConfigOverride[0] = overrideConfig;

        overrideApplicationDisplayAdjustments(token, override);
    }

        // Apply the last override to application resources for compatibility. Because the Resources
        // of Display can be from application, e.g.
        //    applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
        // and the deprecated usage:
        //    applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
    /**
     * Applies the last override to application resources for compatibility. Because the Resources
     * of Display can be from application, e.g.
     *   applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
     * and the deprecated usage:
     *   applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
     *
     * @param token The owner and target of the override.
     * @param override The display adjustments override for application resources. If it is null,
     *                 the override of the token will be removed and pop the last one to use.
     */
    private void overrideApplicationDisplayAdjustments(@NonNull IBinder token,
            @Nullable Consumer<DisplayAdjustments> override) {
        final Consumer<DisplayAdjustments> appOverride;
        if (mActiveRotationAdjustments == null) {
            mActiveRotationAdjustments = new ArrayList<>(2);
@@ -3542,8 +3524,13 @@ public final class ActivityThread extends ClientTransactionHandler {
        // The rotation adjustments must be applied before creating the activity, so the activity
        // can get the adjusted display info during creation.
        if (r.mPendingFixedRotationAdjustments != null) {
            handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments,
                    r.overrideConfig);
            // The adjustments should have been set by handleLaunchActivity, so the last one is the
            // override for activity resources.
            if (mActiveRotationAdjustments != null && !mActiveRotationAdjustments.isEmpty()) {
                mResourcesManager.overrideTokenDisplayAdjustments(r.token,
                        mActiveRotationAdjustments.get(
                                mActiveRotationAdjustments.size() - 1).second);
            }
            r.mPendingFixedRotationAdjustments = null;
        }

@@ -3582,6 +3569,13 @@ public final class ActivityThread extends ClientTransactionHandler {
            mProfiler.startProfiling();
        }

        if (r.mPendingFixedRotationAdjustments != null) {
            // The rotation adjustments must be applied before handling configuration, so process
            // level display metrics can be adjusted.
            overrideApplicationDisplayAdjustments(r.token, adjustments ->
                    adjustments.setFixedRotationAdjustments(r.mPendingFixedRotationAdjustments));
        }

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

@@ -5777,7 +5771,15 @@ public final class ActivityThread extends ClientTransactionHandler {
            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
                    + config);

            mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
            final Resources appResources = mInitialApplication.getResources();
            if (appResources.hasOverrideDisplayAdjustments()) {
                // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments,
                // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated
                // configuration also needs to set to the adjustments for consistency.
                appResources.getDisplayAdjustments().getConfiguration().updateFrom(config);
            }
            mResourcesManager.applyConfigurationToResourcesLocked(config, compat,
                    appResources.getDisplayAdjustments());
            updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                    mResourcesManager.getConfiguration().getLocales());

@@ -7390,7 +7392,8 @@ public final class ActivityThread extends ClientTransactionHandler {
                // We need to apply this change to the resources immediately, because upon returning
                // the view hierarchy will be informed about it.
                if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig,
                        null /* compat */)) {
                        null /* compat */,
                        mInitialApplication.getResources().getDisplayAdjustments())) {
                    updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                            mResourcesManager.getConfiguration().getLocales());

+13 −2
Original line number Diff line number Diff line
@@ -1047,12 +1047,18 @@ public class ResourcesManager {
    public final boolean applyConfigurationToResources(@NonNull Configuration config,
            @Nullable CompatibilityInfo compat) {
        synchronized(this) {
            return applyConfigurationToResourcesLocked(config, compat);
            return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
        }
    }

    public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
            @Nullable CompatibilityInfo compat) {
        return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
    }

    /** Applies the global configuration to the managed resources. */
    public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
            @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                    "ResourcesManager#applyConfigurationToResourcesLocked");
@@ -1076,6 +1082,11 @@ public class ResourcesManager {
                        | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
            }

            if (adjustments != null) {
                // Currently the only case where the adjustment takes effect is to simulate placing
                // an app in a rotated display.
                adjustments.adjustGlobalAppMetrics(defaultDisplayMetrics);
            }
            Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);

            ApplicationPackageManager.configurationChanged();
+31 −9
Original line number Diff line number Diff line
@@ -130,14 +130,16 @@ public class DisplayAdjustments {
        w = metrics.noncompatWidthPixels;
        metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
        metrics.noncompatHeightPixels = w;
    }

        float x = metrics.xdpi;
        metrics.xdpi = metrics.ydpi;
        metrics.ydpi = x;

        x = metrics.noncompatXdpi;
        metrics.noncompatXdpi = metrics.noncompatYdpi;
        metrics.noncompatYdpi = x;
    /** Adjusts global display metrics that is available to applications. */
    public void adjustGlobalAppMetrics(@NonNull DisplayMetrics metrics) {
        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
        if (rotationAdjustments == null) {
            return;
        }
        metrics.noncompatWidthPixels = metrics.widthPixels = rotationAdjustments.mAppWidth;
        metrics.noncompatHeightPixels = metrics.heightPixels = rotationAdjustments.mAppHeight;
    }

    /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
@@ -178,7 +180,7 @@ public class DisplayAdjustments {

    /**
     * An application can be launched in different rotation than the real display. This class
     * provides the information to adjust the values returned by {@link #Display}.
     * provides the information to adjust the values returned by {@link Display}.
     * @hide
     */
    public static class FixedRotationAdjustments implements Parcelable {
@@ -186,12 +188,24 @@ public class DisplayAdjustments {
        @Surface.Rotation
        final int mRotation;

        /**
         * The rotated {@link DisplayInfo#appWidth}. The value cannot be simply swapped according
         * to rotation because it minus the region of screen decorations.
         */
        final int mAppWidth;

        /** The rotated {@link DisplayInfo#appHeight}. */
        final int mAppHeight;

        /** Non-null if the device has cutout. */
        @Nullable
        final DisplayCutout mRotatedDisplayCutout;

        public FixedRotationAdjustments(@Surface.Rotation int rotation, DisplayCutout cutout) {
        public FixedRotationAdjustments(@Surface.Rotation int rotation, int appWidth, int appHeight,
                DisplayCutout cutout) {
            mRotation = rotation;
            mAppWidth = appWidth;
            mAppHeight = appHeight;
            mRotatedDisplayCutout = cutout;
        }

@@ -199,6 +213,8 @@ public class DisplayAdjustments {
        public int hashCode() {
            int hash = 17;
            hash = hash * 31 + mRotation;
            hash = hash * 31 + mAppWidth;
            hash = hash * 31 + mAppHeight;
            hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
            return hash;
        }
@@ -210,12 +226,14 @@ public class DisplayAdjustments {
            }
            final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
            return mRotation == other.mRotation
                    && mAppWidth == other.mAppWidth && mAppHeight == other.mAppHeight
                    && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
        }

        @Override
        public String toString() {
            return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
                    + " appWidth=" + mAppWidth + " appHeight=" + mAppHeight
                    + " cutout=" + mRotatedDisplayCutout + "}";
        }

@@ -227,12 +245,16 @@ public class DisplayAdjustments {
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mRotation);
            dest.writeInt(mAppWidth);
            dest.writeInt(mAppHeight);
            dest.writeTypedObject(
                    new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
        }

        private FixedRotationAdjustments(Parcel in) {
            mRotation = in.readInt();
            mAppWidth = in.readInt();
            mAppHeight = in.readInt();
            final DisplayCutout.ParcelableWrapper cutoutWrapper =
                    in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
            mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
+3 −2
Original line number Diff line number Diff line
@@ -191,7 +191,7 @@ public class TransactionParcelTests {
        PersistableBundle persistableBundle = new PersistableBundle();
        persistableBundle.putInt("k", 4);
        FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments(
                Surface.ROTATION_90, DisplayCutout.NO_CUTOUT);
                Surface.ROTATION_90, 1920, 1080, DisplayCutout.NO_CUTOUT);

        LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
                config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
@@ -351,7 +351,8 @@ public class TransactionParcelTests {
        ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(),
                null /* activityToken */);
        transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(),
                new FixedRotationAdjustments(Surface.ROTATION_270, DisplayCutout.NO_CUTOUT)));
                new FixedRotationAdjustments(Surface.ROTATION_270, 1920, 1080,
                        DisplayCutout.NO_CUTOUT)));

        writeAndPrepareForReading(transaction);

+14 −4
Original line number Diff line number Diff line
@@ -77,8 +77,10 @@ public class DisplayAdjustmentsTests {
        final int realRotation = Surface.ROTATION_0;
        final int fixedRotation = Surface.ROTATION_90;

        mDisplayAdjustments.setFixedRotationAdjustments(
                new FixedRotationAdjustments(fixedRotation, null /* cutout */));
        final int appWidth = 1080;
        final int appHeight = 1920;
        mDisplayAdjustments.setFixedRotationAdjustments(new FixedRotationAdjustments(
                fixedRotation, appWidth, appHeight, null /* cutout */));

        final int w = 1000;
        final int h = 2000;
@@ -95,13 +97,21 @@ public class DisplayAdjustmentsTests {
        metrics.heightPixels = metrics.noncompatHeightPixels = h;

        final DisplayMetrics flippedMetrics = new DisplayMetrics();
        flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = h;
        // The physical dpi should not be adjusted.
        flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = w;
        flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h;
        flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = w;
        flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = h;
        flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w;

        mDisplayAdjustments.adjustMetrics(metrics, realRotation);

        assertEquals(flippedMetrics, metrics);

        mDisplayAdjustments.adjustGlobalAppMetrics(metrics);

        assertEquals(appWidth, metrics.widthPixels);
        assertEquals(appWidth, metrics.noncompatWidthPixels);
        assertEquals(appHeight, metrics.heightPixels);
        assertEquals(appHeight, metrics.noncompatHeightPixels);
    }
}
Loading