Loading core/java/android/window/DisplayAreaOrganizer.java +12 −0 Original line number Diff line number Diff line Loading @@ -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 */ Loading services/core/java/com/android/server/wm/DisplayArea.java +5 −1 Original line number Diff line number Diff line Loading @@ -586,7 +586,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { }; 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) { Loading services/core/java/com/android/server/wm/DisplayContent.java +36 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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 Loading Loading @@ -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 Loading services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +28 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 */); Loading Loading
core/java/android/window/DisplayAreaOrganizer.java +12 −0 Original line number Diff line number Diff line Loading @@ -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 */ Loading
services/core/java/com/android/server/wm/DisplayArea.java +5 −1 Original line number Diff line number Diff line Loading @@ -586,7 +586,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { }; 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) { Loading
services/core/java/com/android/server/wm/DisplayContent.java +36 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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 Loading Loading @@ -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 Loading
services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +28 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading
services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +36 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 */); Loading