From f68fbed31124933773be699828b3c956d337b3cd Mon Sep 17 00:00:00 2001 From: Romica Iordan Date: Thu, 13 May 2021 17:05:24 +0300 Subject: Enable vendors to organize ImeContainer Add FEATURE_IME as unique id for ImeContainer to organize it from WmShell. Disable automatic ImeContainer surface and container reparenting when it is organized. These changes enable vendors to control where the IMEs are positioned and implement custom UX rules (e.g. for foldable devices - place the IME on the bottom screen while the focused app is on the top one). Manual test: Organize FEATURE_IME from WmShell. Set its bounds, appBounds, screenSizeDp and setSmallestScreenWidthDp. Outcome: * The IME receives onConfigurationChanges. * The IME is updated on the screen. * The IME is not reparented from its default parent. Test: atest WmTests:WindowProcessControllerTests Test: atest WmTests:DisplayContentTests Test: atest WmTests:WindowContainerTests Test: atest WmTests:DisplayAreaTest Test: atest WmTests:DualDisplayAreaGroupPolicyTest Bug: 188038793 Change-Id: I438253e9647ceab5b7847b8db66db8399271723f --- core/java/android/window/DisplayAreaOrganizer.java | 12 +++++++ .../java/com/android/server/wm/DisplayArea.java | 6 +++- .../java/com/android/server/wm/DisplayContent.java | 40 +++++++++++++++++++--- .../com/android/server/wm/DisplayContentTests.java | 28 +++++++++++++++ .../server/wm/DualDisplayAreaGroupPolicyTest.java | 36 +++++++++++++++++++ 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index 6758a3b411a2..974a1dd50cf5 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -108,6 +108,18 @@ public class DisplayAreaOrganizer extends WindowOrganizer { */ public static final int FEATURE_ONE_HANDED_BACKGROUND_PANEL = FEATURE_SYSTEM_FIRST + 8; + /** + * Display area hosting IME window tokens (@see ImeContainer). By default, IMEs are parented + * to FEATURE_IME_PLACEHOLDER but can be reparented under other RootDisplayArea. + * + * This feature can register organizers in order to disable the reparenting logic and manage + * the position and settings of the container manually. This is useful for foldable devices + * which require custom UX rules for the IME position (e.g. IME on one screen and the focused + * app on another screen). + * @hide + */ + public static final int FEATURE_IME = FEATURE_SYSTEM_FIRST + 9; + /** * The last boundary of display area for system features */ diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 63fb7939ae60..132396bbf441 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -586,7 +586,11 @@ public class DisplayArea extends WindowContainer { }; Tokens(WindowManagerService wms, Type type, String name) { - super(wms, type, name, FEATURE_WINDOW_TOKENS); + this(wms, type, name, FEATURE_WINDOW_TOKENS); + } + + Tokens(WindowManagerService wms, Type type, String name, int featureId) { + super(wms, type, name, featureId); } void addChild(WindowToken token) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4cb70fd011ed..42c81248da34 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -78,6 +78,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.DisplayAreaOrganizer.FEATURE_IME; import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION; @@ -632,7 +633,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @interface InputMethodTarget {} /** The surface parent of the IME container. */ - private SurfaceControl mInputMethodSurfaceParent; + @VisibleForTesting + SurfaceControl mInputMethodSurfaceParent; /** The screenshot IME surface to place on the task while transitioning to the next task. */ SurfaceControl mImeScreenshot; @@ -3831,6 +3833,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } boolean shouldImeAttachedToApp() { + if (mImeWindowsContainer.isOrganized()) { + return false; + } + // Force attaching IME to the display when magnifying, or it would be magnified with // target app together. final boolean allowAttachToApp = (mMagnificationSpec == null); @@ -3959,8 +3965,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mImeLayeringTarget = target; // 1. Reparent the IME container window to the target root DA to get the correct bounds and - // config. (Only happens when the target window is in a different root DA) - if (target != null) { + // config. Only happens when the target window is in a different root DA and ImeContainer + // is not organized (see FEATURE_IME and updateImeParent). + if (target != null && !mImeWindowsContainer.isOrganized()) { RootDisplayArea targetRoot = target.getRootDisplayArea(); if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()) { // Reposition the IME container to the target root to get the correct bounds and @@ -4144,6 +4151,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void updateImeParent() { + if (mImeWindowsContainer.isOrganized()) { + if (DEBUG_INPUT_METHOD) { + Slog.i(TAG_WM, "ImeContainer is organized. Skip updateImeParent."); + } + // Leave the ImeContainer where the DisplayAreaPolicy placed it. + // FEATURE_IME is organized by vendor so they are responible for placing the surface. + mInputMethodSurfaceParent = null; + return; + } + final SurfaceControl newParent = computeImeParent(); if (newParent != null && newParent != mInputMethodSurfaceParent) { mInputMethodSurfaceParent = newParent; @@ -4750,7 +4767,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean mNeedsLayer = false; ImeContainer(WindowManagerService wms) { - super(wms, Type.ABOVE_TASKS, "ImeContainer"); + super(wms, Type.ABOVE_TASKS, "ImeContainer", FEATURE_IME); } public void setNeedsLayer() { @@ -4811,6 +4828,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp super.assignRelativeLayer(t, relativeTo, layer, forceUpdate); mNeedsLayer = false; } + + @Override + void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) { + super.setOrganizer(organizer, skipDisplayAreaAppeared); + mDisplayContent.updateImeParent(); + } } @Override @@ -4909,6 +4932,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private void assignRelativeLayerForIme(SurfaceControl.Transaction t, boolean forceUpdate) { + if (mImeWindowsContainer.isOrganized()) { + if (DEBUG_INPUT_METHOD) { + Slog.i(TAG_WM, "ImeContainer is organized. Skip assignRelativeLayerForIme."); + } + // Leave the ImeContainer where the DisplayAreaPolicy placed it. + // When using FEATURE_IME, Organizer assumes the responsibility for placing the surface. + return; + } + mImeWindowsContainer.setNeedsLayer(); final WindowState imeTarget = mImeLayeringTarget; // In the case where we have an IME target that is not in split-screen mode IME diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 562e95855001..08be15e16b37 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -102,6 +102,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.when; import android.app.ActivityTaskManager; import android.app.WindowConfiguration; @@ -134,6 +135,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.WindowManager; +import android.window.IDisplayAreaOrganizer; import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; @@ -390,6 +392,32 @@ public class DisplayContentTests extends WindowTestsBase { assertNull("computeImeParent() should be null", mDisplayContent.computeImeParent()); } + @Test + public void testUpdateImeParent_skipForOrganizedImeContainer() { + final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + + final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, + "startingWin"); + startingWin.setHasSurface(true); + assertTrue(startingWin.canBeImeTarget()); + final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class); + doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent(); + + // Main precondition for this test: organize the ImeContainer. + final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class); + when(mockImeOrganizer.asBinder()).thenReturn(new Binder()); + imeContainer.setOrganizer(mockImeOrganizer); + + mDisplayContent.updateImeParent(); + + assertNull("Don't reparent the surface of an organized ImeContainer.", + mDisplayContent.mInputMethodSurfaceParent); + + // Clean up organizer. + imeContainer.setOrganizer(null); + } + /** * This tests root task movement between displays and proper root task's, task's and app token's * display container references updates. diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index 4509ff48206e..dbb7fae548b7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -41,14 +41,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; import android.platform.test.annotations.Presubmit; import android.view.Display; +import android.window.IDisplayAreaOrganizer; import androidx.test.filters.SmallTest; @@ -345,6 +348,39 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */); } + @Test + public void testPlaceImeContainer_skipReparentForOrganizedImeContainer() { + setupImeWindow(); + final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); + final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); + + // By default, the ime container is attached to DC as defined in DAPolicy. + assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); + assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); + + final WindowState firstActivityWin = + createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, + "firstActivityWin"); + spyOn(firstActivityWin); + // firstActivityWin should be the target + doReturn(true).when(firstActivityWin).canBeImeTarget(); + + // Main precondition for this test: organize the ImeContainer. + final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class); + when(mockImeOrganizer.asBinder()).thenReturn(new Binder()); + imeContainer.setOrganizer(mockImeOrganizer); + + WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); + + // The IME target must be updated but the don't reparent organized ImeContainers. + // See DisplayAreaOrganizer#FEATURE_IME. + assertThat(imeTarget).isEqualTo(firstActivityWin); + verify(mFirstRoot, never()).placeImeContainer(imeContainer); + + // Clean up organizer. + imeContainer.setOrganizer(null); + } + @Test public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); -- cgit v1.2.3-59-g8ed1b