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

Commit 3148276a authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Use cached insets for display switch

The insets of common bars in previous device state will be cached.
When display switch transition is running, the previous cached
insets can be dispatched to each window before the insets provider
update new insets for new device state. That reduces intermediate
insets change and extra redraw.

The preserved insets only takes effect during display switch
transition is running.

Also cache PrivacyIndicatorBounds because it is also a member of
InsetsState that will trigger changes.

For an activity which doesn't consume insets when unfolding,
the amount of reduced change events:
 View#onLayout: from 3 to 1
 View#dispatchApplyWindowInsets: from 4 to 2

Bug: 266197298
Flag: com.android.window.flags.use_cached_insets_for_display_switch
Test: atest DisplayPolicyTests#testSwitchDecorInsets
Test: Check the number of calls to View#onLayout and
      View.OnApplyWindowInsetsListener during fold/unfold.
Change-Id: I3a5b458624cee9daaa38dadff1c52ae03ee8d5a8
parent 40aa32b3
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -50,6 +50,17 @@ flag {
  }
}

flag {
  name: "use_cached_insets_for_display_switch"
  namespace: "windowing_frontend"
  description: "Reduce intermediate insets changes for display switch"
  bug: "266197298"
  is_fixed_read_only: true
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "edge_to_edge_by_default"
  namespace: "windowing_frontend"
+58 −0
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ import android.view.InsetsFlags;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
@@ -2121,6 +2122,8 @@ public class DisplayPolicy {
        }

        private static class Cache {
            static final int TYPE_REGULAR_BARS = WindowInsets.Type.statusBars()
                    | WindowInsets.Type.navigationBars();
            /**
             * 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.
@@ -2130,6 +2133,14 @@ public class DisplayPolicy {
            int mPreserveId;
            boolean mActive;

            /**
             * When display switches, mRegularBarsInsets will assign to mPreservedInsets, and the
             * insets sources of previous device state will copy to mRegularBarsInsets.
             */
            ArrayList<InsetsSource> mPreservedInsets;
            ArrayList<InsetsSource> mRegularBarsInsets;
            PrivacyIndicatorBounds mPrivacyIndicatorBounds;

            Cache(DisplayContent dc) {
                mDecorInsets = new DecorInsets(dc);
            }
@@ -2138,6 +2149,17 @@ public class DisplayPolicy {
                return mPreserveId == ID_UPDATING_CONFIG || mDecorInsets.mDisplayContent
                        .mTransitionController.inTransition(mPreserveId);
            }

            static ArrayList<InsetsSource> copyRegularBarInsets(InsetsState srcState) {
                final ArrayList<InsetsSource> state = new ArrayList<>();
                for (int i = srcState.sourceSize() - 1; i >= 0; i--) {
                    final InsetsSource source = srcState.sourceAt(i);
                    if ((source.getType() & TYPE_REGULAR_BARS) != 0) {
                        state.add(new InsetsSource(source));
                    }
                }
                return state;
            }
        }
    }

@@ -2213,23 +2235,59 @@ public class DisplayPolicy {
    @VisibleForTesting
    void updateCachedDecorInsets() {
        DecorInsets prevCache = null;
        PrivacyIndicatorBounds privacyIndicatorBounds = null;
        if (mCachedDecorInsets == null) {
            mCachedDecorInsets = new DecorInsets.Cache(mDisplayContent);
        } else {
            prevCache = new DecorInsets(mDisplayContent);
            prevCache.setTo(mCachedDecorInsets.mDecorInsets);
            privacyIndicatorBounds = mCachedDecorInsets.mPrivacyIndicatorBounds;
            mCachedDecorInsets.mPreservedInsets = mCachedDecorInsets.mRegularBarsInsets;
        }
        // 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);
        if (com.android.window.flags.Flags.useCachedInsetsForDisplaySwitch()) {
            mCachedDecorInsets.mRegularBarsInsets = DecorInsets.Cache.copyRegularBarInsets(
                    mDisplayContent.mDisplayFrames.mInsetsState);
            mCachedDecorInsets.mPrivacyIndicatorBounds =
                    mDisplayContent.mCurrentPrivacyIndicatorBounds;
        } else {
            mCachedDecorInsets.mRegularBarsInsets = null;
            mCachedDecorInsets.mPrivacyIndicatorBounds = null;
        }
        // Switch current to previous cache.
        if (prevCache != null) {
            mDecorInsets.setTo(prevCache);
            if (privacyIndicatorBounds != null) {
                mDisplayContent.mCurrentPrivacyIndicatorBounds = privacyIndicatorBounds;
            }
            mCachedDecorInsets.mActive = true;
        }
    }

    /**
     * This returns a new InsetsState with replacing the insets in target device state when the
     * display is switching (e.g. fold/unfold). Otherwise, it returns the original state. This is
     * to avoid dispatching old insets source before the insets providers update new insets.
     */
    InsetsState replaceInsetsSourcesIfNeeded(InsetsState originalState, boolean copyState) {
        if (mCachedDecorInsets == null || mCachedDecorInsets.mPreservedInsets == null
                || !shouldKeepCurrentDecorInsets()) {
            return originalState;
        }
        final ArrayList<InsetsSource> preservedSources = mCachedDecorInsets.mPreservedInsets;
        final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
        for (int i = preservedSources.size() - 1; i >= 0; i--) {
            final InsetsSource cacheSource = preservedSources.get(i);
            if (state.peekSource(cacheSource.getId()) != null) {
                state.addSource(new InsetsSource(cacheSource));
            }
        }
        return state;
    }

    /**
     * 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
+1 −0
Original line number Diff line number Diff line
@@ -229,6 +229,7 @@ class InsetsPolicy {
            state = originalState;
        }
        state = adjustVisibilityForIme(target, state, state == originalState);
        state = mPolicy.replaceInsetsSourcesIfNeeded(state, state == originalState);
        return adjustInsetsForRoundedCorners(target.mToken, state, state == originalState);
    }

+25 −1
Original line number Diff line number Diff line
@@ -362,7 +362,19 @@ public class DisplayPolicyTests extends WindowTestsBase {

    @Test
    public void testSwitchDecorInsets() {
        createNavBarWithProvidedInsets(mDisplayContent);
        final WindowState win = createApplicationWindow();
        final WindowState bar = createNavBarWithProvidedInsets(mDisplayContent);
        bar.getFrame().set(0, mDisplayContent.mDisplayFrames.mHeight - NAV_BAR_HEIGHT,
                mDisplayContent.mDisplayFrames.mWidth, mDisplayContent.mDisplayFrames.mHeight);
        final int insetsId = bar.mAttrs.providedInsets[0].getId();
        final InsetsSourceProvider provider = mDisplayContent.getInsetsStateController()
                .getOrCreateSourceProvider(insetsId, bar.mAttrs.providedInsets[0].getType());
        provider.setServerVisible(true);
        provider.updateSourceFrame(bar.getFrame());

        final InsetsState prevInsetsState = new InsetsState();
        prevInsetsState.addSource(new InsetsSource(provider.getSource()));

        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
        final DisplayInfo info = mDisplayContent.getDisplayInfo();
        final int w = info.logicalWidth;
@@ -385,6 +397,18 @@ public class DisplayPolicyTests extends WindowTestsBase {
        // The current insets are restored from cache directly.
        assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
                info.logicalWidth, info.logicalHeight).mConfigFrame);
        // Assume that the InsetsSource in current InsetsState is not updated yet. And it will be
        // replaced by the one in cache.
        InsetsState currentInsetsState = new InsetsState();
        final InsetsSource prevSource = new InsetsSource(provider.getSource());
        prevSource.getFrame().scale(0.5f);
        currentInsetsState.addSource(prevSource);
        currentInsetsState = mDisplayContent.getInsetsPolicy().adjustInsetsForWindow(
                win, currentInsetsState);
        if (com.android.window.flags.Flags.useCachedInsetsForDisplaySwitch()) {
            assertEquals(prevInsetsState.peekSource(insetsId),
                    currentInsetsState.peekSource(insetsId));
        }

        // If screen is not fully turned on, then the cache should be preserved.
        displayPolicy.screenTurnedOff(false /* acquireSleepToken */);