From 5d2533cdfc9152204511518fcf5e72738166fd8b Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Thu, 6 May 2021 13:54:05 +0800 Subject: [PATCH] Adjust split layout with IME animation in split (3/N) Listen to IME position changes in SplitLayout to make sure it updates divider view status after re-inflated. Also add functions to get IME position of split which is needed for integrating IME animation with new split implementations. Bug: 179262787 Test: atest WMShellUnitTests Test: observed divider bar won't change to interactive after rotating devices with IME shown. Change-Id: Ic01c23e61ffb666cfc292e56dd6dcf4646be749b --- .../android/wm/shell/apppairs/AppPair.java | 23 +++++- .../wm/shell/common/split/DividerView.java | 12 +-- .../wm/shell/common/split/SplitLayout.java | 78 +++++++++++++++---- .../common/split/SplitWindowManager.java | 13 ++-- .../shell/splitscreen/StageCoordinator.java | 23 +++++- .../shell/common/split/SplitLayoutTests.java | 19 +++-- .../common/split/SplitWindowManagerTests.java | 4 +- 7 files changed, 124 insertions(+), 48 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index 83af53f962f37..abcde34ed5bbf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -20,6 +20,9 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager; @@ -47,7 +50,7 @@ import java.io.PrintWriter; * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair. * Also includes all UI for managing the pair like the divider. */ -class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChangeListener { +class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayoutHandler { private static final String TAG = AppPair.class.getSimpleName(); private ActivityManager.RunningTaskInfo mRootTaskInfo; @@ -106,7 +109,8 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan mSplitLayout = new SplitLayout(TAG + "SplitDivider", mDisplayController.getDisplayContext(mRootTaskInfo.displayId), mRootTaskInfo.configuration, this /* layoutChangeListener */, - b -> b.setParent(mRootTaskLeash), mDisplayImeController); + b -> b.setParent(mRootTaskLeash), mDisplayImeController, + mController.getTaskOrganizer()); final WindowContainerToken token1 = task1.token; final WindowContainerToken token2 = task2.token; @@ -216,6 +220,21 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan } } + @Override + public int getSplitItemPosition(WindowContainerToken token) { + if (token == null) { + return SPLIT_POSITION_UNDEFINED; + } + + if (token.equals(mTaskInfo1.getToken())) { + return SPLIT_POSITION_TOP_OR_LEFT; + } else if (token.equals(mTaskInfo2.getToken())) { + return SPLIT_POSITION_BOTTOM_OR_RIGHT; + } + + return SPLIT_POSITION_UNDEFINED; + } + @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { if (taskInfo.taskId == getRootTaskId()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 442e7a4c6796c..a920f9c08de47 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -36,13 +36,11 @@ import androidx.annotation.Nullable; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; -import com.android.wm.shell.common.DisplayImeController; /** * Divider for multi window splits. */ -public class DividerView extends FrameLayout implements View.OnTouchListener, - DisplayImeController.ImePositionProcessor { +public class DividerView extends FrameLayout implements View.OnTouchListener { public static final long TOUCH_ANIMATION_DURATION = 150; public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200; @@ -98,12 +96,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener, setOnTouchListener(this); } - @Override - public void onImeVisibilityChanged(int displayId, boolean isShowing) { - if (displayId != getDisplay().getDisplayId()) return; - setInteractive(!isShowing); - } - @Override public boolean onTouch(View v, MotionEvent event) { if (mSplitLayout == null || !mInteractive) { @@ -217,7 +209,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener, mViewHost.relayout(lp); } - private void setInteractive(boolean interactive) { + void setInteractive(boolean interactive) { if (interactive == mInteractive) return; mInteractive = interactive; releaseTouching(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 8e8473172597b..c1ba6a5356fad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -28,14 +28,17 @@ import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.view.SurfaceControl; import android.view.WindowInsets; import android.view.WindowManager; +import android.window.WindowContainerToken; import androidx.annotation.Nullable; import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.DisplayImeController; @@ -77,8 +80,11 @@ public final class SplitLayout { private final Rect mDividerBounds = new Rect(); private final Rect mBounds1 = new Rect(); private final Rect mBounds2 = new Rect(); - private final LayoutChangeListener mLayoutChangeListener; + private final SplitLayoutHandler mSplitLayoutHandler; private final SplitWindowManager mSplitWindowManager; + private final DisplayImeController mDisplayImeController; + private final ImePositionProcessor mImePositionProcessor; + private final ShellTaskOrganizer mTaskOrganizer; private Context mContext; private DividerSnapAlgorithm mDividerSnapAlgorithm; @@ -86,18 +92,21 @@ public final class SplitLayout { private boolean mInitialized = false; public SplitLayout(String windowName, Context context, Configuration configuration, - LayoutChangeListener layoutChangeListener, + SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, - DisplayImeController displayImeController) { + DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer) { mContext = context.createConfigurationContext(configuration); - mLayoutChangeListener = layoutChangeListener; + mSplitLayoutHandler = splitLayoutHandler; + mDisplayImeController = displayImeController; mSplitWindowManager = new SplitWindowManager( - windowName, mContext, configuration, parentContainerCallbacks, - displayImeController); + windowName, mContext, configuration, parentContainerCallbacks); + mTaskOrganizer = taskOrganizer; + mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId()); - mDividerWindowWidth = context.getResources().getDimensionPixelSize( + final Resources resources = context.getResources(); + mDividerWindowWidth = resources.getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_thickness); - mDividerInsets = context.getResources().getDimensionPixelSize( + mDividerInsets = resources.getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_insets); mDividerSize = mDividerWindowWidth - mDividerInsets * 2; @@ -179,6 +188,7 @@ public final class SplitLayout { if (mInitialized) return; mInitialized = true; mSplitWindowManager.init(this); + mDisplayImeController.addPositionProcessor(mImePositionProcessor); } /** Releases the surface holding the current {@link DividerView}. */ @@ -186,6 +196,7 @@ public final class SplitLayout { if (!mInitialized) return; mInitialized = false; mSplitWindowManager.release(); + mDisplayImeController.removePositionProcessor(mImePositionProcessor); } /** @@ -194,14 +205,14 @@ public final class SplitLayout { */ void updateDivideBounds(int position) { updateBounds(position); - mLayoutChangeListener.onBoundsChanging(this); mSplitWindowManager.setResizingSplits(true); + mSplitLayoutHandler.onBoundsChanging(this); } void setDividePosition(int position) { mDividePosition = position; updateBounds(mDividePosition); - mLayoutChangeListener.onBoundsChanged(this); + mSplitLayoutHandler.onBoundsChanged(this); mSplitWindowManager.setResizingSplits(false); } @@ -218,11 +229,11 @@ public final class SplitLayout { public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { switch (snapTarget.flag) { case FLAG_DISMISS_START: - mLayoutChangeListener.onSnappedToDismiss(false /* bottomOrRight */); + mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */); mSplitWindowManager.setResizingSplits(false); break; case FLAG_DISMISS_END: - mLayoutChangeListener.onSnappedToDismiss(true /* bottomOrRight */); + mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */); mSplitWindowManager.setResizingSplits(false); break; default: @@ -232,7 +243,7 @@ public final class SplitLayout { } void onDoubleTappedDivider() { - mLayoutChangeListener.onDoubleTappedDivider(); + mSplitLayoutHandler.onDoubleTappedDivider(); } /** @@ -291,8 +302,8 @@ public final class SplitLayout { return bounds.width() > bounds.height(); } - /** Listens layout change event. */ - public interface LayoutChangeListener { + /** Handles layout change event. */ + public interface SplitLayoutHandler { /** Calls when dismissing split. */ void onSnappedToDismiss(boolean snappedToEnd); @@ -305,5 +316,42 @@ public final class SplitLayout { /** Calls when user double tapped on the divider bar. */ default void onDoubleTappedDivider() { } + + /** Returns split position of the token. */ + @SplitPosition + int getSplitItemPosition(WindowContainerToken token); + } + + /** Records IME top offset changes and updates SplitLayout correspondingly. */ + private class ImePositionProcessor implements DisplayImeController.ImePositionProcessor { + + private final int mDisplayId; + + private ImePositionProcessor(int displayId) { + mDisplayId = displayId; + } + + @Override + public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean showing, boolean isFloating, SurfaceControl.Transaction t) { + if (displayId != mDisplayId) return 0; + final int imeTargetPosition = getImeTargetPosition(); + if (!mInitialized || imeTargetPosition == SPLIT_POSITION_UNDEFINED) return 0; + + // Make {@link DividerView} non-interactive while IME showing in split mode. Listen to + // ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough + // because DividerView won't receive onImeVisibilityChanged callback after it being + // re-inflated. + mSplitWindowManager.setInteractive( + !showing || imeTargetPosition == SPLIT_POSITION_UNDEFINED); + + return 0; + } + + @SplitPosition + private int getImeTargetPosition() { + final WindowContainerToken token = mTaskOrganizer.getImeTarget(mDisplayId); + return mSplitLayoutHandler.getSplitItemPosition(token); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java index f6efb0120dda5..0cea0efc0057b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java @@ -46,7 +46,6 @@ import android.view.WindowlessWindowManager; import androidx.annotation.Nullable; import com.android.wm.shell.R; -import com.android.wm.shell.common.DisplayImeController; /** * Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split. @@ -55,7 +54,6 @@ public final class SplitWindowManager extends WindowlessWindowManager { private static final String TAG = SplitWindowManager.class.getSimpleName(); private final String mWindowName; - private final DisplayImeController mDisplayImeController; private final ParentContainerCallbacks mParentContainerCallbacks; private Context mContext; private SurfaceControlViewHost mViewHost; @@ -68,13 +66,11 @@ public final class SplitWindowManager extends WindowlessWindowManager { } public SplitWindowManager(String windowName, Context context, Configuration config, - ParentContainerCallbacks parentContainerCallbacks, - DisplayImeController displayImeController) { + ParentContainerCallbacks parentContainerCallbacks) { super(config, null /* rootSurface */, null /* hostInputToken */); mContext = context.createConfigurationContext(config); mParentContainerCallbacks = parentContainerCallbacks; mWindowName = windowName; - mDisplayImeController = displayImeController; } @Override @@ -128,7 +124,6 @@ public final class SplitWindowManager extends WindowlessWindowManager { lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; mViewHost.setView(mDividerView, lp); mDividerView.setup(splitLayout, mViewHost); - mDisplayImeController.addPositionProcessor(mDividerView); } /** @@ -137,7 +132,6 @@ public final class SplitWindowManager extends WindowlessWindowManager { */ void release() { if (mDividerView != null) { - mDisplayImeController.removePositionProcessor(mDividerView); mDividerView = null; } @@ -152,6 +146,11 @@ public final class SplitWindowManager extends WindowlessWindowManager { } } + void setInteractive(boolean interactive) { + if (mDividerView == null) return; + mDividerView.setInteractive(interactive); + } + void setResizingSplits(boolean resizing) { if (resizing == mResizingSplits) return; try { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index e20feae2d522a..e1799d28e4236 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.transitTypeToString; import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -53,6 +54,7 @@ import android.window.DisplayAreaInfo; import android.window.IRemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.R; @@ -85,8 +87,8 @@ import java.util.List; * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and * {@link #onStageHasChildrenChanged(StageListenerImpl).} */ -class StageCoordinator implements SplitLayout.LayoutChangeListener, - RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler { +class StageCoordinator implements SplitLayout.SplitLayoutHandler, + RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler { private static final String TAG = StageCoordinator.class.getSimpleName(); @@ -564,6 +566,21 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, .setWindowCrop(mSideStage.mRootLeash, null)); } + @Override + public int getSplitItemPosition(WindowContainerToken token) { + if (token == null) { + return SPLIT_POSITION_UNDEFINED; + } + + if (token.equals(mMainStage.mRootTaskInfo.getToken())) { + return getMainStagePosition(); + } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) { + return getSideStagePosition(); + } + + return SPLIT_POSITION_UNDEFINED; + } + @Override public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) { mDisplayAreaInfo = displayAreaInfo; @@ -571,7 +588,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, mDisplayAreaInfo.configuration, this, b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b), - mDisplayImeController); + mDisplayImeController, mTaskOrganizer); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 7b0e6b9a5ed7a..952dc31cdaee6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -35,6 +35,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.policy.DividerSnapAlgorithm; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayImeController; @@ -48,9 +49,10 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class SplitLayoutTests extends ShellTestCase { - @Mock SplitLayout.LayoutChangeListener mLayoutChangeListener; + @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler; @Mock SurfaceControl mRootLeash; @Mock DisplayImeController mDisplayImeController; + @Mock ShellTaskOrganizer mTaskOrganizer; private SplitLayout mSplitLayout; @Before @@ -60,9 +62,10 @@ public class SplitLayoutTests extends ShellTestCase { "TestSplitLayout", mContext, getConfiguration(false), - mLayoutChangeListener, + mSplitLayoutHandler, b -> b.setParent(mRootLeash), - mDisplayImeController); + mDisplayImeController, + mTaskOrganizer); } @Test @@ -76,19 +79,19 @@ public class SplitLayoutTests extends ShellTestCase { @Test public void testUpdateDivideBounds() { mSplitLayout.updateDivideBounds(anyInt()); - verify(mLayoutChangeListener).onBoundsChanging(any(SplitLayout.class)); + verify(mSplitLayoutHandler).onBoundsChanging(any(SplitLayout.class)); } @Test public void testSetDividePosition() { mSplitLayout.setDividePosition(anyInt()); - verify(mLayoutChangeListener).onBoundsChanged(any(SplitLayout.class)); + verify(mSplitLayoutHandler).onBoundsChanged(any(SplitLayout.class)); } @Test public void testOnDoubleTappedDivider() { mSplitLayout.onDoubleTappedDivider(); - verify(mLayoutChangeListener).onDoubleTappedDivider(); + verify(mSplitLayoutHandler).onDoubleTappedDivider(); } @Test @@ -98,11 +101,11 @@ public class SplitLayoutTests extends ShellTestCase { DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START); mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); - verify(mLayoutChangeListener).onSnappedToDismiss(eq(false)); + verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false)); snapTarget = getSnapTarget(0 /* position */, DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END); mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); - verify(mLayoutChangeListener).onSnappedToDismiss(eq(true)); + verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true)); } private static Configuration getConfiguration(boolean isLandscape) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java index 86d0d82222e45..698315a77d8e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java @@ -29,7 +29,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayImeController; import org.junit.Before; import org.junit.Test; @@ -43,7 +42,6 @@ import org.mockito.MockitoAnnotations; public class SplitWindowManagerTests extends ShellTestCase { @Mock SurfaceControl mSurfaceControl; @Mock SplitLayout mSplitLayout; - @Mock DisplayImeController mDisplayImeController; private SplitWindowManager mSplitWindowManager; @Before @@ -52,7 +50,7 @@ public class SplitWindowManagerTests extends ShellTestCase { final Configuration configuration = new Configuration(); configuration.setToDefaults(); mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration, - b -> b.setParent(mSurfaceControl), mDisplayImeController); + b -> b.setParent(mSurfaceControl)); when(mSplitLayout.getDividerBounds()).thenReturn( new Rect(0, 0, configuration.windowConfiguration.getBounds().width(), configuration.windowConfiguration.getBounds().height()));