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

Commit 79e9167e authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Cache decor insets for display switch

In general this can reduce the amount of display configuration change
from 2~4 times to 1 when switching physical display, e.g. fold/unfold.

The multiple configuration changes are from the insets provider
windows that change their provided insets size which can affect
display configuration. Or even the windows can be added or removed
that also triggers the change.

The concept:
 1. The first time fold->unfold:
    Cache folded insets.
    Still perform multiple config changes to calculate unfold.
 2. Unfold->fold:
    The decor insets in folded state is known, use it directly.
    Cache unfolded insets.
 3. Fold->unfold:
    Cached unfolded insets is available, similar as 2.

Bug: 266197298
Test: DisplayPolicyTests#testSwitchDecorInsets

Change-Id: Icb832bad8c6a7412b6fc0ecba318d2034681d3f7
parent 1c5290f2
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -2962,6 +2962,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
                mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(mDisplayId,
                        mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight);
                mDisplayRotation.physicalDisplayChanged();
                mDisplayPolicy.physicalDisplayChanged();
            }

            // If there is an override set for base values - use it, otherwise use new values.
@@ -2993,6 +2994,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            reconfigureDisplayLocked();

            if (physicalDisplayChanged) {
                mDisplayPolicy.physicalDisplayUpdated();
                mDisplaySwitchTransitionLauncher.onDisplayUpdated(currentRotation, getRotation(),
                        getDisplayAreaInfo());
            }
@@ -3042,7 +3044,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
                        + mBaseDisplayHeight + " on display:" + getDisplayId());
            }
        }
        if (mDisplayReady) {
        if (mDisplayReady && !mDisplayPolicy.shouldKeepCurrentDecorInsets()) {
            mDisplayPolicy.mDecorInsets.invalidate();
        }
    }
+120 −11
Original line number Diff line number Diff line
@@ -173,6 +173,10 @@ public class DisplayPolicy {

    private static final int INSETS_OVERRIDE_INDEX_INVALID = -1;

    // TODO(b/266197298): Remove this by a more general protocol from the insets providers.
    private static final boolean USE_CACHED_INSETS_FOR_DISPLAY_SWITCH =
            SystemProperties.getBoolean("persist.wm.debug.cached_insets_switch", false);

    private final WindowManagerService mService;
    private final Context mContext;
    private final Context mUiContext;
@@ -217,6 +221,8 @@ public class DisplayPolicy {
    private final SystemGesturesPointerEventListener mSystemGestures;

    final DecorInsets mDecorInsets;
    /** Currently it can only be non-null when physical display switch happens. */
    private DecorInsets.Cache mCachedDecorInsets;

    private volatile int mLidState = LID_ABSENT;
    private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -1695,11 +1701,7 @@ public class DisplayPolicy {
     * Called when the configuration has changed, and it's safe to load new values from resources.
     */
    public void onConfigurationChanged() {
        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();

        final Resources res = getCurrentUserResources();
        final int portraitRotation = displayRotation.getPortraitRotation();

        mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
        mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
        mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
@@ -1870,10 +1872,11 @@ public class DisplayPolicy {

            @Override
            public String toString() {
                return "{nonDecorInsets=" + mNonDecorInsets
                        + ", configInsets=" + mConfigInsets
                        + ", nonDecorFrame=" + mNonDecorFrame
                        + ", configFrame=" + mConfigFrame + '}';
                final StringBuilder tmpSb = new StringBuilder(32);
                return "{nonDecorInsets=" + mNonDecorInsets.toShortString(tmpSb)
                        + ", configInsets=" + mConfigInsets.toShortString(tmpSb)
                        + ", nonDecorFrame=" + mNonDecorFrame.toShortString(tmpSb)
                        + ", configFrame=" + mConfigFrame.toShortString(tmpSb) + '}';
            }
        }

@@ -1911,6 +1914,39 @@ public class DisplayPolicy {
                info.mNeedUpdate = true;
            }
        }

        void setTo(DecorInsets src) {
            for (int i = mInfoForRotation.length - 1; i >= 0; i--) {
                mInfoForRotation[i].set(src.mInfoForRotation[i]);
            }
        }

        void dump(String prefix, PrintWriter pw) {
            for (int rotation = 0; rotation < mInfoForRotation.length; rotation++) {
                final DecorInsets.Info info = mInfoForRotation[rotation];
                pw.println(prefix + Surface.rotationToString(rotation) + "=" + info);
            }
        }

        private static class Cache {
            /**
             * If {@link #mPreserveId} is this value, it is in the middle of updating display
             * configuration before a transition is started. Then the active cache should be used.
             */
            static final int ID_UPDATING_CONFIG = -1;
            final DecorInsets mDecorInsets;
            int mPreserveId;
            boolean mActive;

            Cache(DisplayContent dc) {
                mDecorInsets = new DecorInsets(dc);
            }

            boolean canPreserve() {
                return mPreserveId == ID_UPDATING_CONFIG || mDecorInsets.mDisplayContent
                        .mTransitionController.inTransition(mPreserveId);
            }
        }
    }

    /**
@@ -1918,6 +1954,9 @@ public class DisplayPolicy {
     * call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
     */
    boolean updateDecorInsetsInfo() {
        if (shouldKeepCurrentDecorInsets()) {
            return false;
        }
        final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
        final int rotation = displayFrames.mRotation;
        final int dw = displayFrames.mWidth;
@@ -1928,6 +1967,10 @@ public class DisplayPolicy {
        if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
            return false;
        }
        if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve()
                && !mDisplayContent.isSleeping()) {
            mCachedDecorInsets = null;
        }
        mDecorInsets.invalidate();
        mDecorInsets.mInfoForRotation[rotation].set(newInfo);
        return true;
@@ -1937,6 +1980,71 @@ public class DisplayPolicy {
        return mDecorInsets.get(rotation, w, h);
    }

    /** Returns {@code true} to trust that {@link #mDecorInsets} already has the expected state. */
    boolean shouldKeepCurrentDecorInsets() {
        return mCachedDecorInsets != null && mCachedDecorInsets.mActive
                && mCachedDecorInsets.canPreserve();
    }

    void physicalDisplayChanged() {
        if (USE_CACHED_INSETS_FOR_DISPLAY_SWITCH) {
            updateCachedDecorInsets();
        }
    }

    /**
     * Caches the current insets and switches current insets to previous cached insets. This is to
     * reduce multiple display configuration changes if there are multiple insets provider windows
     * which may trigger {@link #updateDecorInsetsInfo()} individually.
     */
    @VisibleForTesting
    void updateCachedDecorInsets() {
        DecorInsets prevCache = null;
        if (mCachedDecorInsets == null) {
            mCachedDecorInsets = new DecorInsets.Cache(mDisplayContent);
        } else {
            prevCache = new DecorInsets(mDisplayContent);
            prevCache.setTo(mCachedDecorInsets.mDecorInsets);
        }
        // Set a special id to preserve it before a real id is available from transition.
        mCachedDecorInsets.mPreserveId = DecorInsets.Cache.ID_UPDATING_CONFIG;
        // Cache the current insets.
        mCachedDecorInsets.mDecorInsets.setTo(mDecorInsets);
        // Switch current to previous cache.
        if (prevCache != null) {
            mDecorInsets.setTo(prevCache);
            mCachedDecorInsets.mActive = true;
        }
    }

    /**
     * Called after the display configuration is updated according to the physical change. Suppose
     * there should be a display change transition, so associate the cached decor insets with the
     * transition to limit the lifetime of using the cache.
     */
    void physicalDisplayUpdated() {
        if (mCachedDecorInsets == null) {
            return;
        }
        if (!mDisplayContent.mTransitionController.isCollecting()) {
            // Unable to know when the display switch is finished.
            mCachedDecorInsets = null;
            return;
        }
        mCachedDecorInsets.mPreserveId =
                mDisplayContent.mTransitionController.getCollectingTransitionId();
        // The validator will run after the transition is finished. So if the insets are changed
        // during the transition, it can update to the latest state.
        mDisplayContent.mTransitionController.mStateValidators.add(() -> {
            // The insets provider client may defer to change its window until screen is on. So
            // only validate when awake to avoid the cache being always dropped.
            if (!mDisplayContent.isSleeping() && updateDecorInsetsInfo()) {
                Slog.d(TAG, "Insets changed after display switch transition");
                mDisplayContent.sendNewConfiguration();
            }
        });
    }

    @NavigationBarPosition
    int navigationBarPosition(int displayRotation) {
        if (mNavigationBar != null) {
@@ -2613,9 +2721,10 @@ public class DisplayPolicy {
        pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
        pw.println(mRemoteInsetsControllerControlsSystemBars);
        pw.print(prefix); pw.println("mDecorInsetsInfo:");
        for (int rotation = 0; rotation < mDecorInsets.mInfoForRotation.length; rotation++) {
            final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
            pw.println(prefixInner + Surface.rotationToString(rotation) + "=" + info);
        mDecorInsets.dump(prefixInner, pw);
        if (mCachedDecorInsets != null) {
            pw.print(prefix); pw.println("mCachedDecorInsets:");
            mCachedDecorInsets.mDecorInsets.dump(prefixInner, pw);
        }
        mSystemGestures.dump(pw, prefix);

+13 −0
Original line number Diff line number Diff line
@@ -431,6 +431,19 @@ class TransitionController {
        return inCollectingTransition(wc) || inPlayingTransition(wc);
    }

    /** Returns {@code true} if the id matches a collecting or playing transition. */
    boolean inTransition(int syncId) {
        if (mCollectingTransition != null && mCollectingTransition.getSyncId() == syncId) {
            return true;
        }
        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
            if (mPlayingTransitions.get(i).getSyncId() == syncId) {
                return true;
            }
        }
        return false;
    }

    /** @return {@code true} if wc is in a participant subtree */
    boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
        if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
+27 −0
Original line number Diff line number Diff line
@@ -298,6 +298,33 @@ public class DisplayPolicyTests extends WindowTestsBase {
                DisplayPolicy.isOverlappingWithNavBar(targetWin));
    }

    @Test
    public void testSwitchDecorInsets() {
        createNavBarWithProvidedInsets(mDisplayContent);
        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
        final DisplayInfo info = mDisplayContent.getDisplayInfo();
        final int w = info.logicalWidth;
        final int h = info.logicalHeight;
        displayPolicy.updateDecorInsetsInfo();
        final Rect prevConfigFrame = new Rect(displayPolicy.getDecorInsetsInfo(info.rotation,
                info.logicalWidth, info.logicalHeight).mConfigFrame);

        displayPolicy.updateCachedDecorInsets();
        mDisplayContent.updateBaseDisplayMetrics(w / 2, h / 2,
                info.logicalDensityDpi, info.physicalXDpi, info.physicalYDpi);
        // There is no previous cache. But the current state will be cached.
        assertFalse(displayPolicy.shouldKeepCurrentDecorInsets());

        // Switch to original state.
        displayPolicy.updateCachedDecorInsets();
        mDisplayContent.updateBaseDisplayMetrics(w, h,
                info.logicalDensityDpi, info.physicalXDpi, info.physicalYDpi);
        assertTrue(displayPolicy.shouldKeepCurrentDecorInsets());
        // The current insets are restored from cache directly.
        assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
                info.logicalWidth, info.logicalHeight).mConfigFrame);
    }

    @Test
    public void testUpdateDisplayConfigurationByDecor() {
        doReturn(NO_CUTOUT).when(mDisplayContent).calculateDisplayCutoutForRotation(anyInt());