From 5cf6507e517b184bc25d9dcd416f99d702fcea7e Mon Sep 17 00:00:00 2001 From: tomnatan Date: Mon, 1 Nov 2021 17:33:13 +0000 Subject: [PATCH] Update size compat restart button surface position after display insests changed Fix: 204532180 Fix: 182905588 Test: atest WMShellUnitTests:SizeCompatUIControllerTest Test: atest WMShellUnitTests:SizeCompatUILayoutTest Change-Id: Icda54db231783f576832b22e4d286fe0524a2088 Merged-In: Icda54db231783f576832b22e4d286fe0524a2088 --- .../sizecompatui/SizeCompatUIController.java | 74 ++++++++++++++++++- .../sizecompatui/SizeCompatUILayout.java | 24 +++--- .../SizeCompatUIControllerTest.java | 51 ++++++++++++- .../sizecompatui/SizeCompatUILayoutTest.java | 34 ++++++++- .../systemui/wmshell/WMShellBaseModule.java | 7 +- 5 files changed, 172 insertions(+), 18 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java index ab3cbd655ea18..04d974a09cd7a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java @@ -24,11 +24,16 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.view.Display; +import android.view.InsetsSourceControl; +import android.view.InsetsState; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; @@ -42,7 +47,7 @@ import java.util.function.Consumer; * Controls to show/update restart-activity buttons on Tasks based on whether the foreground * activities are in size compatibility mode. */ -public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener, +public class SizeCompatUIController implements OnDisplaysChangedListener, DisplayImeController.ImePositionProcessor { /** Callback for size compat UI interaction. */ @@ -58,6 +63,10 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang /** Whether the IME is shown on display id. */ private final Set mDisplaysWithIme = new ArraySet<>(1); + /** {@link PerDisplayOnInsetsChangedListener} by display id. */ + private final SparseArray mOnInsetsChangedListeners = + new SparseArray<>(0); + /** The showing UIs by task id. */ private final SparseArray mActiveLayouts = new SparseArray<>(0); @@ -66,6 +75,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang private final Context mContext; private final DisplayController mDisplayController; + private final DisplayInsetsController mDisplayInsetsController; private final DisplayImeController mImeController; private final SyncTransactionQueue mSyncQueue; @@ -76,10 +86,12 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang public SizeCompatUIController(Context context, DisplayController displayController, + DisplayInsetsController displayInsetsController, DisplayImeController imeController, SyncTransactionQueue syncQueue) { mContext = context; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mImeController = imeController; mSyncQueue = syncQueue; mDisplayController.addDisplayWindowListener(this); @@ -114,9 +126,15 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang } } + @Override + public void onDisplayAdded(int displayId) { + addOnInsetsChangedListener(displayId); + } + @Override public void onDisplayRemoved(int displayId) { mDisplayContextCache.remove(displayId); + removeOnInsetsChangedListener(displayId); // Remove all size compat UIs on the removed display. final List toRemoveTaskIds = new ArrayList<>(); @@ -126,8 +144,29 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang } } + private void addOnInsetsChangedListener(int displayId) { + PerDisplayOnInsetsChangedListener listener = new PerDisplayOnInsetsChangedListener( + displayId); + listener.register(); + mOnInsetsChangedListeners.put(displayId, listener); + } + + private void removeOnInsetsChangedListener(int displayId) { + PerDisplayOnInsetsChangedListener listener = mOnInsetsChangedListeners.get(displayId); + if (listener == null) { + return; + } + listener.unregister(); + mOnInsetsChangedListeners.remove(displayId); + } + + @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + updateDisplayLayout(displayId); + } + + private void updateDisplayLayout(int displayId) { final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout)); } @@ -219,4 +258,37 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang } } } + + /** An implementation of {@link OnInsetsChangedListener} for a given display id. */ + private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener { + final int mDisplayId; + final InsetsState mInsetsState = new InsetsState(); + + PerDisplayOnInsetsChangedListener(int displayId) { + mDisplayId = displayId; + } + + void register() { + mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this); + } + + void unregister() { + mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this); + } + + @Override + public void insetsChanged(InsetsState insetsState) { + if (mInsetsState.equals(insetsState)) { + return; + } + mInsetsState.set(insetsState); + updateDisplayLayout(mDisplayId); + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + insetsChanged(insetsState); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java index bebb6d30c47f4..1a2c94fd4747e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java @@ -55,6 +55,7 @@ class SizeCompatUILayout { private final int mTaskId; private ShellTaskOrganizer.TaskListener mTaskListener; private DisplayLayout mDisplayLayout; + private final Rect mStableBounds; private final int mButtonWidth; private final int mButtonHeight; private final int mPopupOffsetX; @@ -89,6 +90,9 @@ class SizeCompatUILayout { mShouldShowHint = !hasShownHint; mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this); + mStableBounds = new Rect(); + mDisplayLayout.getStableBounds(mStableBounds); + final Resources resources = mContext.getResources(); mButtonWidth = resources.getDimensionPixelSize(R.dimen.size_compat_button_width); mButtonHeight = resources.getDimensionPixelSize(R.dimen.size_compat_button_height); @@ -173,8 +177,7 @@ class SizeCompatUILayout { if (!taskConfig.windowConfiguration.getBounds() .equals(prevTaskConfig.windowConfiguration.getBounds())) { // Reposition the UI surfaces. - updateButtonSurfacePosition(); - updateHintSurfacePosition(); + updateAllSurfacePositions(); } if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) { @@ -190,19 +193,14 @@ class SizeCompatUILayout { /** Called when display layout changed. */ void updateDisplayLayout(DisplayLayout displayLayout) { - if (displayLayout == mDisplayLayout) { - return; - } - - final Rect prevStableBounds = new Rect(); + final Rect prevStableBounds = mStableBounds; final Rect curStableBounds = new Rect(); - mDisplayLayout.getStableBounds(prevStableBounds); displayLayout.getStableBounds(curStableBounds); mDisplayLayout = displayLayout; if (!prevStableBounds.equals(curStableBounds)) { // Stable bounds changed, update UI surface positions. - updateButtonSurfacePosition(); - updateHintSurfacePosition(); + updateAllSurfacePositions(); + mStableBounds.set(curStableBounds); } } @@ -268,6 +266,11 @@ class SizeCompatUILayout { createSizeCompatHint(); } + private void updateAllSurfacePositions() { + updateButtonSurfacePosition(); + updateHintSurfacePosition(); + } + @VisibleForTesting void updateButtonSurfacePosition() { if (mButton == null || mButtonWindowManager.getSurfaceControl() == null) { @@ -290,6 +293,7 @@ class SizeCompatUILayout { updateSurfacePosition(leash, positionX, positionY); } + @VisibleForTesting void updateHintSurfacePosition() { if (mHint == null || mHintWindowManager == null || mHintWindowManager.getSurfaceControl() == null) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java index 8839f58ea8894..d12cf5c3518af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java @@ -16,11 +16,14 @@ package com.android.wm.shell.sizecompatui; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -28,6 +31,8 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.res.Configuration; import android.testing.AndroidTestingRunner; +import android.view.InsetsSource; +import android.view.InsetsState; import androidx.test.filters.SmallTest; @@ -35,12 +40,16 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -58,12 +67,16 @@ public class SizeCompatUIControllerTest extends ShellTestCase { private SizeCompatUIController mController; private @Mock DisplayController mMockDisplayController; + private @Mock DisplayInsetsController mMockDisplayInsetsController; private @Mock DisplayLayout mMockDisplayLayout; private @Mock DisplayImeController mMockImeController; private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener; private @Mock SyncTransactionQueue mMockSyncQueue; private @Mock SizeCompatUILayout mMockLayout; + @Captor + ArgumentCaptor mOnInsetsChangedListenerCaptor; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -72,7 +85,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase { doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId(); doReturn(TASK_ID).when(mMockLayout).getTaskId(); mController = new SizeCompatUIController(mContext, mMockDisplayController, - mMockImeController, mMockSyncQueue) { + mMockDisplayInsetsController, mMockImeController, mMockSyncQueue) { @Override SizeCompatUILayout createLayout(Context context, int displayId, int taskId, Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) { @@ -113,8 +126,18 @@ public class SizeCompatUIControllerTest extends ShellTestCase { verify(mMockLayout).release(); } + @Test + public void testOnDisplayAdded() { + mController.onDisplayAdded(DISPLAY_ID); + mController.onDisplayAdded(DISPLAY_ID + 1); + + verify(mMockDisplayInsetsController).addInsetsChangedListener(eq(DISPLAY_ID), any()); + verify(mMockDisplayInsetsController).addInsetsChangedListener(eq(DISPLAY_ID + 1), any()); + } + @Test public void testOnDisplayRemoved() { + mController.onDisplayAdded(DISPLAY_ID); final Configuration taskConfig = new Configuration(); mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener); @@ -122,9 +145,12 @@ public class SizeCompatUIControllerTest extends ShellTestCase { mController.onDisplayRemoved(DISPLAY_ID + 1); verify(mMockLayout, never()).release(); + verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID), + any()); mController.onDisplayRemoved(DISPLAY_ID); + verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any()); verify(mMockLayout).release(); } @@ -144,6 +170,29 @@ public class SizeCompatUIControllerTest extends ShellTestCase { verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout); } + @Test + public void testInsetsChanged() { + mController.onDisplayAdded(DISPLAY_ID); + final Configuration taskConfig = new Configuration(); + mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, + mMockTaskListener); + InsetsState insetsState = new InsetsState(); + InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + insetsSource.setFrame(0, 0, 1000, 1000); + insetsState.addSource(insetsSource); + + verify(mMockDisplayInsetsController).addInsetsChangedListener(eq(DISPLAY_ID), + mOnInsetsChangedListenerCaptor.capture()); + mOnInsetsChangedListenerCaptor.getValue().insetsChanged(insetsState); + + verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout); + + // No update if the insets state is the same. + clearInvocations(mMockLayout); + mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState)); + verify(mMockLayout, never()).updateDisplayLayout(mMockDisplayLayout); + } + @Test public void testChangeButtonVisibilityOnImeShowHide() { final Configuration taskConfig = new Configuration(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java index ee4c81547bbd2..2ba603c8eda8b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java @@ -16,6 +16,8 @@ package com.android.wm.shell.sizecompatui; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertFalse; @@ -33,6 +35,8 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.view.DisplayInfo; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.View; @@ -77,7 +81,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase { mTaskConfig = new Configuration(); mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext, - new Configuration(), TASK_ID, mTaskListener, mDisplayLayout, + new Configuration(), TASK_ID, mTaskListener, new DisplayLayout(), false /* hasShownHint */); spyOn(mLayout); @@ -176,7 +180,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase { displayInfo.logicalWidth = 1000; displayInfo.logicalHeight = 2000; final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, - mContext.getResources(), false, false); + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); mLayout.updateDisplayLayout(displayLayout1); verify(mLayout).updateButtonSurfacePosition(); @@ -185,12 +189,36 @@ public class SizeCompatUILayoutTest extends ShellTestCase { // No update if the display bounds is the same. clearInvocations(mLayout); final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, - mContext.getResources(), false, false); + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); mLayout.updateDisplayLayout(displayLayout2); verify(mLayout, never()).updateButtonSurfacePosition(); verify(mLayout, never()).updateHintSurfacePosition(); } + @Test + public void testUpdateDisplayLayoutInsets() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false); + + mLayout.updateDisplayLayout(displayLayout); + verify(mLayout).updateButtonSurfacePosition(); + verify(mLayout).updateHintSurfacePosition(); + + // Update if the insets change on the existing display layout + clearInvocations(mLayout); + InsetsState insetsState = new InsetsState(); + InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + insetsSource.setFrame(0, 0, 1000, 1000); + insetsState.addSource(insetsSource); + displayLayout.setInsets(mContext.getResources(), insetsState); + mLayout.updateDisplayLayout(displayLayout); + verify(mLayout).updateButtonSurfacePosition(); + verify(mLayout).updateHintSurfacePosition(); + } + @Test public void testUpdateImeVisibility() { // Create button if it is not created. diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 0a33930d404cf..9786464b834bd 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -151,9 +151,10 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static SizeCompatUIController provideSizeCompatUIController(Context context, - DisplayController displayController, DisplayImeController imeController, - SyncTransactionQueue syncQueue) { - return new SizeCompatUIController(context, displayController, imeController, syncQueue); + DisplayController displayController, DisplayInsetsController displayInsetsController, + DisplayImeController imeController, SyncTransactionQueue syncQueue) { + return new SizeCompatUIController(context, displayController, displayInsetsController, + imeController, syncQueue); } @WMSingleton