From 837fd13486d12383e174674ebce871687237882c Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Tue, 8 Sep 2020 23:34:15 +0800 Subject: [PATCH] Use current fullscreen opaque window as insets control target This fixes stats bar blinking when switching from an app that hides status bar to another that shows status bar. It is caused by the TopFullscreenOpaqueWindow in DisplayPolicy will be updated according to visibility. There may be an intermediate state that the insets control target is still the previous window, then the transient showing bar will be hidden a while. It is tempting to make canAffectSystemUiFlags accept the activity with mVisibleRequested=true so TopFullscreenOpaqueWindow can be updated earlier. But that will break status bar hiding animation when switching from an app that shows the bar to another that hides the bar. Also fix: - The original insets source is modified when getting insets state for client because the default "new InsetsState" will reuse the given insets sources. - Redundant invocation of updateBarControlTarget when calling abortTransient from updateBarControlTarget. Fixes: 168023253 Test: InsetsPolicyTest#testControlsForDispatch_topAppHidesStatusBar Change-Id: I60f29dba81b0273a189ddd0a48a2e84155177f5b --- .../com/android/server/wm/InsetsPolicy.java | 34 ++++++++++++++++--- .../server/wm/TaskSnapshotController.java | 4 +-- .../server/wm/TaskSnapshotSurface.java | 6 +--- .../com/android/server/wm/WindowState.java | 4 +++ .../android/server/wm/InsetsPolicyTest.java | 31 +++++++++++++++++ 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 0c9c87a088008..361788497e116 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -36,6 +36,7 @@ import android.view.InsetsAnimationControlCallbacks; import android.view.InsetsAnimationControlImpl; import android.view.InsetsAnimationControlRunner; import android.view.InsetsController; +import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; @@ -207,11 +208,21 @@ class InsetsPolicy { * @see InsetsStateController#getInsetsForDispatch */ InsetsState getInsetsForDispatch(WindowState target) { - InsetsState originalState = mStateController.getInsetsForDispatch(target); + final InsetsState originalState = mStateController.getInsetsForDispatch(target); InsetsState state = originalState; for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) { - state = new InsetsState(state); - state.setSourceVisible(mShowingTransientTypes.get(i), false); + final int type = mShowingTransientTypes.get(i); + final InsetsSource originalSource = state.peekSource(type); + if (originalSource != null && originalSource.isVisible()) { + if (state == originalState) { + // The source will be modified, create a non-deep copy to store the new one. + state = new InsetsState(originalState); + } + // Replace the source with a copy in invisible state. + final InsetsSource source = new InsetsSource(originalSource); + source.setVisible(false); + state.addSource(source); + } } return state; } @@ -247,11 +258,14 @@ class InsetsPolicy { } } + /** + * If the caller is not {@link #updateBarControlTarget}, it should call + * updateBarControlTarget(mFocusedWin) after this invocation. + */ private void abortTransient() { mPolicy.getStatusBarManagerInternal().abortTransient(mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray()); mShowingTransientTypes.clear(); - updateBarControlTarget(mFocusedWin); } private @Nullable InsetsControlTarget getFakeControlTarget(@Nullable WindowState focused, @@ -285,7 +299,7 @@ class InsetsPolicy { // fake control to the client, so that it can re-show the bar during this scenario. return mDummyControlTarget; } - if (mPolicy.topAppHidesStatusBar()) { + if (!canBeTopFullscreenOpaqueWindow(focusedWin) && mPolicy.topAppHidesStatusBar()) { // Non-fullscreen focused window should not break the state that the top-fullscreen-app // window hides status bar. return mPolicy.getTopFullscreenOpaqueWindow(); @@ -293,6 +307,16 @@ class InsetsPolicy { return focusedWin; } + private static boolean canBeTopFullscreenOpaqueWindow(@Nullable WindowState win) { + // The condition doesn't use WindowState#canAffectSystemUiFlags because the window may + // haven't drawn or committed the visibility. + final boolean nonAttachedAppWindow = win != null + && win.mAttrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && win.mAttrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + return nonAttachedAppWindow && win.mAttrs.isFullscreen() && !win.isFullyTransparent() + && !win.inMultiWindowMode(); + } + private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin, boolean forceShowsSystemBarsForWindowingMode) { if (mShowingTransientTypes.indexOf(ITYPE_NAVIGATION_BAR) != -1) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index b475846d60edb..fd4289cc83355 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -481,9 +481,7 @@ class TaskSnapshotController { final int color = ColorUtils.setAlphaComponent( task.getTaskDescription().getBackgroundColor(), 255); final LayoutParams attrs = mainWindow.getAttrs(); - final InsetsPolicy insetsPolicy = mainWindow.getDisplayContent().getInsetsPolicy(); - final InsetsState insetsState = - new InsetsState(insetsPolicy.getInsetsForDispatch(mainWindow)); + final InsetsState insetsState = mainWindow.getInsetsState(); mergeInsetsSources(insetsState, mainWindow.getRequestedInsetsState()); final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState); final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index c4ca989e9d6b8..a6744d55af9d5 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -237,11 +237,7 @@ class TaskSnapshotSurface implements StartingSurface { task.getBounds(taskBounds); currentOrientation = topFullscreenOpaqueWindow.getConfiguration().orientation; activityType = activity.getActivityType(); - - final InsetsPolicy insetsPolicy = topFullscreenOpaqueWindow.getDisplayContent() - .getInsetsPolicy(); - insetsState = - new InsetsState(insetsPolicy.getInsetsForDispatch(topFullscreenOpaqueWindow)); + insetsState = topFullscreenOpaqueWindow.getInsetsState(); mergeInsetsSources(insetsState, topFullscreenOpaqueWindow.getRequestedInsetsState()); } try { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index f2f3cefae8a5e..e4a079d84a2a3 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1531,6 +1531,10 @@ class WindowState extends WindowContainer implements WindowManagerP return getDisplayContent().getDisplayInfo(); } + /** + * Returns the insets state for the client. Its sources may be the copies with visibility + * modification according to the state of transient bars. + */ InsetsState getInsetsState() { return getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this); } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 821480daa9306..8c02faf7ac092 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -37,16 +37,21 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import android.app.StatusBarManager; import android.platform.test.annotations.Presubmit; import android.view.InsetsSourceControl; import android.view.InsetsState; import androidx.test.filters.SmallTest; +import com.android.server.statusbar.StatusBarManagerInternal; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -205,6 +210,23 @@ public class InsetsPolicyTest extends WindowTestsBase { assertNotNull(fullscreenAppControls); assertEquals(1, fullscreenAppControls.length); assertEquals(ITYPE_STATUS_BAR, fullscreenAppControls[0].getType()); + + // Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't. + final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp"); + final InsetsState newRequestedState = new InsetsState(); + newRequestedState.getSource(ITYPE_STATUS_BAR).setVisible(true); + newFocusedFullscreenApp.updateRequestedInsetsState(newRequestedState); + // Make sure status bar is hidden by previous insets state. + mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp); + + final StatusBarManagerInternal sbmi = + mDisplayContent.getDisplayPolicy().getStatusBarManagerInternal(); + clearInvocations(sbmi); + mDisplayContent.getInsetsPolicy().updateBarControlTarget(newFocusedFullscreenApp); + // The status bar should be shown by newFocusedFullscreenApp even + // mTopFullscreenOpaqueWindowState is still fullscreenApp. + verify(sbmi).setWindowState(mDisplayContent.mDisplayId, StatusBarManager.WINDOW_STATUS_BAR, + StatusBarManager.WINDOW_STATE_SHOWING); } @UseTestDisplay(addWindows = W_ACTIVITY) @@ -295,6 +317,15 @@ public class InsetsPolicyTest extends WindowTestsBase { final InsetsState state = policy.getInsetsForDispatch(mAppWindow); state.setSourceVisible(ITYPE_STATUS_BAR, true); state.setSourceVisible(ITYPE_NAVIGATION_BAR, true); + + final InsetsState clientState = mAppWindow.getInsetsState(); + // The transient bar states for client should be invisible. + assertFalse(clientState.getSource(ITYPE_STATUS_BAR).isVisible()); + assertFalse(clientState.getSource(ITYPE_NAVIGATION_BAR).isVisible()); + // The original state shouldn't be modified. + assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible()); + assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible()); + policy.onInsetsModified(mAppWindow, state); waitUntilWindowAnimatorIdle();