From 79e9167e93e2c60ca679489839db36089d92600a Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Wed, 10 May 2023 15:10:45 +0000 Subject: [PATCH] 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 --- .../com/android/server/wm/DisplayContent.java | 4 +- .../com/android/server/wm/DisplayPolicy.java | 131 ++++++++++++++++-- .../server/wm/TransitionController.java | 13 ++ .../android/server/wm/DisplayPolicyTests.java | 27 ++++ 4 files changed, 163 insertions(+), 12 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 57812c1d604c4..2b5648f39d2cf 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -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(); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index be52e5a4566b5..0dac7d6cb5464 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -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); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index c9316bf6e9721..9890f135c4629 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -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)) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 353a8ecaf13d8..5d5a3a5e48f2d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -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());