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
This commit is contained in:
Jerry Chang
2021-05-06 13:54:05 +08:00
parent 4c425b2a1e
commit 5d2533cdfc
7 changed files with 124 additions and 48 deletions

View File

@@ -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()) {

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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()));