Merge changes from topic "staged_split_ime" into sc-dev

* changes:
  Adjust split layout with IME animation in split (5/N)
  Adjust split layout with IME animation in split (4/N)
  Adjust split layout with IME animation in split (3/N)
  Adjust split layout with IME animation in split (2/N)
  Adjust split layout with IME animation in split (1/N)
This commit is contained in:
Jerry Chang
2021-05-17 17:00:59 +00:00
committed by Android (Google) Code Review
23 changed files with 503 additions and 244 deletions

View File

@@ -16,14 +16,14 @@
package com.android.wm.shell;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
@@ -145,7 +145,7 @@ public final class ShellCommandHandlerImpl {
}
final int taskId = new Integer(args[2]);
final int sideStagePosition = args.length > 3
? new Integer(args[3]) : STAGE_POSITION_BOTTOM_OR_RIGHT;
? new Integer(args[3]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
return true;
}

View File

@@ -20,11 +20,15 @@ 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;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -35,6 +39,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitLayout;
@@ -45,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;
@@ -54,6 +59,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
private SurfaceControl mTaskLeash1;
private ActivityManager.RunningTaskInfo mTaskInfo2;
private SurfaceControl mTaskLeash2;
private SurfaceControl mDimLayer1;
private SurfaceControl mDimLayer2;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final AppPairsController mController;
private final SyncTransactionQueue mSyncQueue;
@@ -101,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;
@@ -153,9 +162,13 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
mTaskLeash1 = leash;
mSyncQueue.runInSync(t -> mDimLayer1 =
SurfaceUtils.makeDimLayer(t, mTaskLeash1, "Dim layer", mSurfaceSession));
} else if (taskInfo.taskId == getTaskId2()) {
mTaskInfo2 = taskInfo;
mTaskLeash2 = leash;
mSyncQueue.runInSync(t -> mDimLayer2 =
SurfaceUtils.makeDimLayer(t, mTaskLeash2, "Dim layer", mSurfaceSession));
} else {
throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
}
@@ -212,13 +225,32 @@ 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()) {
// We don't want to release this object back to the pool since the root task went away.
mController.unpair(mRootTaskInfo.taskId, false /* releaseToPool */);
} else if (taskInfo.taskId == getTaskId1() || taskInfo.taskId == getTaskId2()) {
} else if (taskInfo.taskId == getTaskId1()) {
mController.unpair(mRootTaskInfo.taskId);
mSyncQueue.runInSync(t -> t.remove(mDimLayer1));
} else if (taskInfo.taskId == getTaskId2()) {
mController.unpair(mRootTaskInfo.taskId);
mSyncQueue.runInSync(t -> t.remove(mDimLayer2));
}
}
@@ -264,40 +296,16 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
@Override
public void onBoundsChanging(SplitLayout layout) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) return;
final Rect dividerBounds = layout.getDividerBounds();
final Rect bounds1 = layout.getBounds1();
final Rect bounds2 = layout.getBounds2();
mSyncQueue.runInSync(t -> t
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
.setPosition(mTaskLeash2, bounds2.left, bounds2.top)
// Sets crop to prevent visible region of tasks overlap with each other when
// re-positioning surfaces while resizing.
.setWindowCrop(mTaskLeash1, bounds1.width(), bounds1.height())
.setWindowCrop(mTaskLeash2, bounds2.width(), bounds2.height()));
mSyncQueue.runInSync(t ->
layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
}
@Override
public void onBoundsChanged(SplitLayout layout) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) return;
final Rect dividerBounds = layout.getDividerBounds();
final Rect bounds1 = layout.getBounds1();
final Rect bounds2 = layout.getBounds2();
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mTaskInfo1.token, bounds1)
.setBounds(mTaskInfo2.token, bounds2);
mController.getTaskOrganizer().applyTransaction(wct);
mSyncQueue.runInSync(t -> t
// Resets layer of divider bar to make sure it is always on top.
.setLayer(dividerLeash, Integer.MAX_VALUE)
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
.setPosition(mTaskLeash2, bounds2.left, bounds2.top)
// Resets crop to apply new surface bounds directly.
.setWindowCrop(mTaskLeash1, null)
.setWindowCrop(mTaskLeash2, null));
layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t ->
layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.common;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
/**
* Helpers for handling surface.
*/
public class SurfaceUtils {
/** Creates a dim layer above indicated host surface. */
public static SurfaceControl makeDimLayer(SurfaceControl.Transaction t, SurfaceControl host,
String name, SurfaceSession surfaceSession) {
SurfaceControl dimLayer = new SurfaceControl.Builder(surfaceSession)
.setParent(host)
.setColorLayer()
.setName(name)
.setCallsite("SurfaceUtils.makeDimLayer")
.build();
t.setLayer(dimLayer, Integer.MAX_VALUE).setColor(dimLayer, new float[]{0f, 0f, 0f});
return dimLayer;
}
}

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

@@ -25,16 +25,22 @@ import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_D
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.app.ActivityManager;
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 android.window.WindowContainerTransaction;
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;
@@ -42,7 +48,32 @@ import com.android.wm.shell.common.DisplayImeController;
* Records and handles layout of splits. Helps to calculate proper bounds when configuration or
* divide position changes.
*/
public class SplitLayout {
public final class SplitLayout {
/**
* Split position isn't specified normally meaning to use what ever it is currently set to.
*/
public static final int SPLIT_POSITION_UNDEFINED = -1;
/**
* Specifies that a split is positioned at the top half of the screen if
* in portrait mode or at the left half of the screen if in landscape mode.
*/
public static final int SPLIT_POSITION_TOP_OR_LEFT = 0;
/**
* Specifies that a split is positioned at the bottom half of the screen if
* in portrait mode or at the right half of the screen if in landscape mode.
*/
public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1;
@IntDef(prefix = {"SPLIT_POSITION_"}, value = {
SPLIT_POSITION_UNDEFINED,
SPLIT_POSITION_TOP_OR_LEFT,
SPLIT_POSITION_BOTTOM_OR_RIGHT
})
public @interface SplitPosition {
}
private final int mDividerWindowWidth;
private final int mDividerInsets;
private final int mDividerSize;
@@ -51,8 +82,11 @@ public 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;
@@ -60,18 +94,21 @@ public 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;
@@ -82,17 +119,17 @@ public class SplitLayout {
/** Gets bounds of the primary split. */
public Rect getBounds1() {
return mBounds1;
return new Rect(mBounds1);
}
/** Gets bounds of the secondary split. */
public Rect getBounds2() {
return mBounds2;
return new Rect(mBounds2);
}
/** Gets bounds of divider window. */
public Rect getDividerBounds() {
return mDividerBounds;
return new Rect(mDividerBounds);
}
/** Returns leash of the current divider bar. */
@@ -153,6 +190,7 @@ public class SplitLayout {
if (mInitialized) return;
mInitialized = true;
mSplitWindowManager.init(this);
mDisplayImeController.addPositionProcessor(mImePositionProcessor);
}
/** Releases the surface holding the current {@link DividerView}. */
@@ -160,6 +198,8 @@ public class SplitLayout {
if (!mInitialized) return;
mInitialized = false;
mSplitWindowManager.release();
mDisplayImeController.removePositionProcessor(mImePositionProcessor);
mImePositionProcessor.reset();
}
/**
@@ -168,14 +208,14 @@ public 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);
}
@@ -192,11 +232,11 @@ public 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:
@@ -206,7 +246,7 @@ public class SplitLayout {
}
void onDoubleTappedDivider() {
mLayoutChangeListener.onDoubleTappedDivider();
mSplitLayoutHandler.onDoubleTappedDivider();
}
/**
@@ -265,8 +305,38 @@ public class SplitLayout {
return bounds.width() > bounds.height();
}
/** Listens layout change event. */
public interface LayoutChangeListener {
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
final Rect dividerBounds = mImePositionProcessor.adjustForIme(mDividerBounds);
final Rect bounds1 = mImePositionProcessor.adjustForIme(mBounds1);
final Rect bounds2 = mImePositionProcessor.adjustForIme(mBounds2);
final SurfaceControl dividerLeash = getDividerLeash();
if (dividerLeash != null) {
t.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
// Resets layer of divider bar to make sure it is always on top.
.setLayer(dividerLeash, Integer.MAX_VALUE);
}
t.setPosition(leash1, bounds1.left, bounds1.top)
.setWindowCrop(leash1, bounds1.width(), bounds1.height());
t.setPosition(leash2, bounds2.left, bounds2.top)
.setWindowCrop(leash2, bounds2.width(), bounds2.height());
mImePositionProcessor.applySurfaceDimValues(t, dimLayer1, dimLayer2);
}
/** Apply recorded task layout to the {@link WindowContainerTransaction}. */
public void applyTaskChanges(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
wct.setBounds(task1.token, mImePositionProcessor.adjustForIme(mBounds1))
.setBounds(task2.token, mImePositionProcessor.adjustForIme(mBounds2));
}
/** Handles layout change event. */
public interface SplitLayoutHandler {
/** Calls when dismissing split. */
void onSnappedToDismiss(boolean snappedToEnd);
@@ -279,5 +349,133 @@ public 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 {
/**
* Maximum size of an adjusted split bounds relative to original stack bounds. Used to
* restrict IME adjustment so that a min portion of top split remains visible.
*/
private static final float ADJUSTED_SPLIT_FRACTION_MAX = 0.7f;
private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
private final int mDisplayId;
private int mYOffsetForIme;
private float mDimValue1;
private float mDimValue2;
private int mStartImeTop;
private int mEndImeTop;
private int mTargetYOffset;
private int mLastYOffset;
private float mTargetDim1;
private float mTargetDim2;
private float mLastDim1;
private float mLastDim2;
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;
mStartImeTop = showing ? hiddenTop : shownTop;
mEndImeTop = showing ? shownTop : hiddenTop;
// Update target dim values
mLastDim1 = mDimValue1;
mTargetDim1 = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT && showing
? ADJUSTED_NONFOCUS_DIM : 0.0f;
mLastDim2 = mDimValue2;
mTargetDim2 = imeTargetPosition == SPLIT_POSITION_TOP_OR_LEFT && showing
? ADJUSTED_NONFOCUS_DIM : 0.0f;
// Calculate target bounds offset for IME
mLastYOffset = mYOffsetForIme;
final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
&& !isFloating && !isLandscape(mRootBounds) && showing;
mTargetYOffset = needOffset ? getTargetYOffset() : 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;
}
@Override
public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
if (displayId != mDisplayId) return;
onProgress(getProgress(imeTop));
mSplitLayoutHandler.onBoundsChanging(SplitLayout.this);
}
@Override
public void onImeEndPositioning(int displayId, boolean cancel,
SurfaceControl.Transaction t) {
if (displayId != mDisplayId || cancel) return;
onProgress(1.0f);
mSplitLayoutHandler.onBoundsChanging(SplitLayout.this);
}
private int getTargetYOffset() {
final int desireOffset = Math.abs(mEndImeTop - mStartImeTop);
// Make sure to keep at least 30% visible for the top split.
final int maxOffset = (int) (mBounds1.height() * ADJUSTED_SPLIT_FRACTION_MAX);
return -Math.min(desireOffset, maxOffset);
}
@SplitPosition
private int getImeTargetPosition() {
final WindowContainerToken token = mTaskOrganizer.getImeTarget(mDisplayId);
return mSplitLayoutHandler.getSplitItemPosition(token);
}
private float getProgress(int currImeTop) {
return ((float) currImeTop - mStartImeTop) / (mEndImeTop - mStartImeTop);
}
private void onProgress(float progress) {
mDimValue1 = getProgressValue(mLastDim1, mTargetDim1, progress);
mDimValue2 = getProgressValue(mLastDim2, mTargetDim2, progress);
mYOffsetForIme =
(int) getProgressValue((float) mLastYOffset, (float) mTargetYOffset, progress);
}
private float getProgressValue(float start, float end, float progress) {
return start + (end - start) * progress;
}
private void reset() {
mYOffsetForIme = 0;
mDimValue1 = mDimValue2 = 0.0f;
}
/* Adjust bounds with IME offset. */
private Rect adjustForIme(Rect bounds) {
final Rect temp = new Rect(bounds);
if (mYOffsetForIme != 0) temp.offset(0, mYOffsetForIme);
return temp;
}
private void applySurfaceDimValues(SurfaceControl.Transaction t, SurfaceControl dimLayer1,
SurfaceControl dimLayer2) {
t.setAlpha(dimLayer1, mDimValue1).setVisibility(dimLayer1, mDimValue1 > 0.001f);
t.setAlpha(dimLayer2, mDimValue2).setVisibility(dimLayer2, mDimValue2 > 0.001f);
}
}
}

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

@@ -29,14 +29,14 @@ import static android.content.Intent.EXTRA_SHORTCUT_ID;
import static android.content.Intent.EXTRA_TASK_ID;
import static android.content.Intent.EXTRA_USER;
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.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -64,7 +64,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.splitscreen.SplitScreen.StagePosition;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -203,10 +203,10 @@ public class DragAndDropPolicy {
final boolean leftOrTop = target.type == TYPE_SPLIT_TOP || target.type == TYPE_SPLIT_LEFT;
@StageType int stage = STAGE_TYPE_UNDEFINED;
@StagePosition int position = STAGE_POSITION_UNDEFINED;
@SplitPosition int position = SPLIT_POSITION_UNDEFINED;
if (target.type != TYPE_FULLSCREEN && mSplitScreen != null) {
// Update launch options for the split side we are targeting.
position = leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
position = leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
if (!inSplitScreen) {
// Launch in the side stage if we are not in split-screen already.
stage = STAGE_TYPE_SIDE;
@@ -219,7 +219,7 @@ public class DragAndDropPolicy {
}
private void startClipDescription(ClipDescription description, Intent intent,
@StageType int stage, @StagePosition int position) {
@StageType int stage, @SplitPosition int position) {
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
@@ -291,12 +291,12 @@ public class DragAndDropPolicy {
* Interface for actually committing the task launches.
*/
public interface Starter {
void startTask(int taskId, @StageType int stage, @StagePosition int position,
void startTask(int taskId, @StageType int stage, @SplitPosition int position,
@Nullable Bundle options);
void startShortcut(String packageName, String shortcutId, @StageType int stage,
@StagePosition int position, @Nullable Bundle options, UserHandle user);
@SplitPosition int position, @Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent, Intent fillInIntent,
@StageType int stage, @StagePosition int position,
@StageType int stage, @SplitPosition int position,
@Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
void exitSplitScreen();

View File

@@ -577,7 +577,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays
mSplits.getSplitTransitions().dismissSplit(
mSplits, mSplitLayout, !toPrimaryTask, snapped);
} else {
mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
onDismissSplit();
}
}

View File

@@ -38,6 +38,7 @@ import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
@@ -70,9 +71,9 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
private final LegacySplitScreenTransitions mSplitTransitions;
LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController,
ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
SyncTransactionQueue syncQueue) {
ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
SyncTransactionQueue syncQueue) {
mSplitScreenController = splitScreenController;
mTaskOrganizer = shellTaskOrganizer;
mSplitTransitions = new LegacySplitScreenTransitions(splitScreenController.mTransactionPool,
@@ -146,21 +147,11 @@ class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG);
// Initialize dim surfaces:
mPrimaryDim = new SurfaceControl.Builder(mSurfaceSession)
.setParent(mPrimarySurface).setColorLayer()
.setName("Primary Divider Dim")
.setCallsite("SplitScreenTaskOrganizer.onTaskAppeared")
.build();
mSecondaryDim = new SurfaceControl.Builder(mSurfaceSession)
.setParent(mSecondarySurface).setColorLayer()
.setName("Secondary Divider Dim")
.setCallsite("SplitScreenTaskOrganizer.onTaskAppeared")
.build();
SurfaceControl.Transaction t = getTransaction();
t.setLayer(mPrimaryDim, Integer.MAX_VALUE);
t.setColor(mPrimaryDim, new float[]{0f, 0f, 0f});
t.setLayer(mSecondaryDim, Integer.MAX_VALUE);
t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f});
mPrimaryDim = SurfaceUtils.makeDimLayer(
t, mPrimarySurface, "Primary Divider Dim", mSurfaceSession);
mSecondaryDim = SurfaceUtils.makeDimLayer(
t, mSecondarySurface, "Secondary Divider Dim", mSurfaceSession);
t.apply();
releaseTransaction(t);
}

View File

@@ -19,6 +19,7 @@ package com.android.wm.shell.splitscreen;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -36,8 +37,9 @@ class MainStage extends StageTaskListener {
private boolean mIsActive = false;
MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) {
super(taskOrganizer, displayId, callbacks, syncQueue);
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession) {
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
}
boolean isActive() {

View File

@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -33,8 +34,9 @@ class SideStage extends StageTaskListener {
private static final String TAG = SideStage.class.getSimpleName();
SideStage(ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) {
super(taskOrganizer, displayId, callbacks, syncQueue);
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession) {
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
}
void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,

View File

@@ -19,6 +19,7 @@ package com.android.wm.shell.splitscreen;
import android.annotation.IntDef;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
/**
* Interface to engage split-screen feature.
@@ -26,29 +27,6 @@ import com.android.wm.shell.common.annotations.ExternalThread;
*/
@ExternalThread
public interface SplitScreen {
/**
* Stage position isn't specified normally meaning to use what ever it is currently set to.
*/
int STAGE_POSITION_UNDEFINED = -1;
/**
* Specifies that a stage is positioned at the top half of the screen if
* in portrait mode or at the left half of the screen if in landscape mode.
*/
int STAGE_POSITION_TOP_OR_LEFT = 0;
/**
* Specifies that a stage is positioned at the bottom half of the screen if
* in portrait mode or at the right half of the screen if in landscape mode.
*/
int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
@IntDef(prefix = { "STAGE_POSITION_" }, value = {
STAGE_POSITION_UNDEFINED,
STAGE_POSITION_TOP_OR_LEFT,
STAGE_POSITION_BOTTOM_OR_RIGHT
})
@interface StagePosition {}
/**
* Stage type isn't specified normally meaning to use what ever the default is.
* E.g. exit split-screen and launch the app in fullscreen.
@@ -75,7 +53,7 @@ public interface SplitScreen {
/** Callback interface for listening to changes in a split-screen stage. */
interface SplitScreenListener {
void onStagePositionChanged(@StageType int stage, @StagePosition int position);
void onStagePositionChanged(@StageType int stage, @SplitPosition int position);
void onTaskStageChanged(int taskId, @StageType int stage, boolean visible);
}

View File

@@ -19,9 +19,9 @@ package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_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.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 +53,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.transition.Transitions;
@@ -122,7 +123,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.isSplitScreenVisible();
}
public boolean moveToSideStage(int taskId, @SplitScreen.StagePosition int sideStagePosition) {
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
@@ -131,7 +132,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SplitScreen.StagePosition int sideStagePosition) {
@SplitPosition int sideStagePosition) {
return mStageCoordinator.moveToSideStage(task, sideStagePosition);
}
@@ -139,7 +140,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.removeFromSideStage(taskId);
}
public void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
public void setSideStagePosition(@SplitPosition int sideStagePosition) {
mStageCoordinator.setSideStagePosition(sideStagePosition);
}
@@ -149,7 +150,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void enterSplitScreen(int taskId, boolean leftOrTop) {
moveToSideStage(taskId,
leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT);
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
}
public void exitSplitScreen() {
@@ -173,7 +174,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void startTask(int taskId, @SplitScreen.StageType int stage,
@SplitScreen.StagePosition int position, @Nullable Bundle options) {
@SplitPosition int position, @Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
try {
@@ -184,7 +185,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void startShortcut(String packageName, String shortcutId,
@SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
@SplitScreen.StageType int stage, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
options = resolveStartStage(stage, position, options);
@@ -199,7 +200,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void startIntent(PendingIntent intent, Intent fillInIntent,
@SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
@SplitScreen.StageType int stage, @SplitPosition int position,
@Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
@@ -211,11 +212,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
private Bundle resolveStartStage(@SplitScreen.StageType int stage,
@SplitScreen.StagePosition int position, @Nullable Bundle options) {
@SplitPosition int position, @Nullable Bundle options) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
// Use the stage of the specified position is valid.
if (position != STAGE_POSITION_UNDEFINED) {
if (position != SPLIT_POSITION_UNDEFINED) {
if (position == mStageCoordinator.getSideStagePosition()) {
options = resolveStartStage(STAGE_TYPE_SIDE, position, options);
} else {
@@ -228,7 +229,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
break;
}
case STAGE_TYPE_SIDE: {
if (position != STAGE_POSITION_UNDEFINED) {
if (position != SPLIT_POSITION_UNDEFINED) {
mStageCoordinator.setSideStagePosition(position);
} else {
position = mStageCoordinator.getSideStagePosition();
@@ -240,10 +241,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
break;
}
case STAGE_TYPE_MAIN: {
if (position != STAGE_POSITION_UNDEFINED) {
if (position != SPLIT_POSITION_UNDEFINED) {
// Set the side stage opposite of what we want to the main stage.
final int sideStagePosition = position == STAGE_POSITION_TOP_OR_LEFT
? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT;
final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
mStageCoordinator.setSideStagePosition(sideStagePosition);
} else {
position = mStageCoordinator.getMainStagePosition();
@@ -418,7 +419,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
@SplitScreen.StagePosition int sidePosition,
@SplitPosition int sidePosition,
@Nullable IRemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,

View File

@@ -25,8 +25,9 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
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;
@@ -47,14 +48,15 @@ import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
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;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -64,6 +66,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
@@ -84,19 +87,22 @@ 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();
/** internal value for mDismissTop that represents no dismiss */
private static final int NO_DISMISS = -2;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final MainStage mMainStage;
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
private @SplitScreen.StagePosition int mSideStagePosition = STAGE_POSITION_BOTTOM_OR_RIGHT;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
private final int mDisplayId;
private SplitLayout mSplitLayout;
@@ -115,8 +121,8 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
/** Whether the device is supporting legacy split or not. */
private boolean mUseLegacySplit;
@SplitScreen.StageType int mDismissTop = NO_DISMISS;
private final Runnable mOnTransitionAnimationComplete = () -> {
// If still playing, let it finish.
if (!isSplitScreenVisible()) {
@@ -137,8 +143,18 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
mSyncQueue = syncQueue;
mRootTDAOrganizer = rootTDAOrganizer;
mTaskOrganizer = taskOrganizer;
mMainStage = new MainStage(mTaskOrganizer, mDisplayId, mMainStageListener, mSyncQueue);
mSideStage = new SideStage(mTaskOrganizer, mDisplayId, mSideStageListener, mSyncQueue);
mMainStage = new MainStage(
mTaskOrganizer,
mDisplayId,
mMainStageListener,
mSyncQueue,
mSurfaceSession);
mSideStage = new SideStage(
mTaskOrganizer,
mDisplayId,
mSideStageListener,
mSyncQueue,
mSurfaceSession);
mDisplayImeController = displayImeController;
mRootTDAOrganizer.registerListener(displayId, this);
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
@@ -176,7 +192,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SplitScreen.StagePosition int sideStagePosition) {
@SplitPosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
setSideStagePosition(sideStagePosition);
mMainStage.activate(getMainStageBounds(), wct);
@@ -201,7 +217,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitScreen.StagePosition int sidePosition,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition,
@Nullable IRemoteTransition remoteTransition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mainOptions = mainOptions != null ? mainOptions : new Bundle();
@@ -225,20 +241,22 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
}
@SplitScreen.StagePosition int getSideStagePosition() {
@SplitLayout.SplitPosition
int getSideStagePosition() {
return mSideStagePosition;
}
@SplitScreen.StagePosition int getMainStagePosition() {
return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT;
@SplitLayout.SplitPosition
int getMainStagePosition() {
return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
}
void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
void setSideStagePosition(@SplitPosition int sideStagePosition) {
setSideStagePosition(sideStagePosition, true /* updateVisibility */);
}
private void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition,
private void setSideStagePosition(@SplitPosition int sideStagePosition,
boolean updateVisibility) {
if (mSideStagePosition == sideStagePosition) return;
mSideStagePosition = sideStagePosition;
@@ -289,7 +307,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
}
void updateActivityOptions(Bundle opts, @SplitScreen.StagePosition int position) {
void updateActivityOptions(Bundle opts, @SplitPosition int position) {
addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
if (!mMainStage.isActive()) {
@@ -487,8 +505,8 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
@Override
public void onSnappedToDismiss(boolean bottomOrRight) {
final boolean mainStageToTop =
bottomOrRight ? mSideStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT;
bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
if (ENABLE_SHELL_TRANSITIONS) {
onSnappedToDismissTransition(mainStageToTop);
return;
@@ -497,55 +515,49 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
@Override
public void onBoundsChanging(SplitLayout layout) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) return;
final Rect mainStageBounds = getMainStageBounds();
final Rect sideStageBounds = getSideStageBounds();
mSyncQueue.runInSync(t -> t
.setPosition(dividerLeash,
mSplitLayout.getDividerBounds().left, mSplitLayout.getDividerBounds().top)
.setPosition(mMainStage.mRootLeash, mainStageBounds.left, mainStageBounds.top)
.setPosition(mSideStage.mRootLeash, sideStageBounds.left, sideStageBounds.top)
// Sets crop to prevent visible region of tasks overlap with each other when
// re-positioning surfaces while resizing.
.setWindowCrop(mMainStage.mRootLeash,
mainStageBounds.width(), mainStageBounds.height())
.setWindowCrop(mSideStage.mRootLeash,
sideStageBounds.width(), sideStageBounds.height()));
public void onDoubleTappedDivider() {
setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT);
}
@Override
public void onDoubleTappedDivider() {
setSideStagePosition(mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT);
public void onBoundsChanging(SplitLayout layout) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
mSyncQueue.runInSync(t -> layout.applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer));
}
@Override
public void onBoundsChanged(SplitLayout layout) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) return;
final Rect mainStageBounds = getMainStageBounds();
final Rect sideStageBounds = getSideStageBounds();
final WindowContainerTransaction wct = new WindowContainerTransaction();
mMainStage.setBounds(mainStageBounds, wct);
mSideStage.setBounds(sideStageBounds, wct);
mTaskOrganizer.applyTransaction(wct);
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
mSyncQueue.runInSync(t -> t
// Resets layer of divider bar to make sure it is always on top.
.setLayer(dividerLeash, Integer.MAX_VALUE)
.setPosition(dividerLeash,
mSplitLayout.getDividerBounds().left, mSplitLayout.getDividerBounds().top)
.setPosition(mMainStage.mRootLeash,
mainStageBounds.left, mainStageBounds.top)
.setPosition(mSideStage.mRootLeash,
sideStageBounds.left, sideStageBounds.top)
// Resets crop to apply new surface bounds directly.
.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null));
final WindowContainerTransaction wct = new WindowContainerTransaction();
layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> layout.applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer));
}
@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
@@ -555,7 +567,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mDisplayAreaInfo.configuration, this,
b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b),
mDisplayImeController);
mDisplayImeController, mTaskOrganizer);
}
}
@@ -574,12 +586,12 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
private Rect getSideStageBounds() {
return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
}
private Rect getMainStageBounds() {
return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
}
@@ -742,7 +754,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
// Update local states (before animating).
setDividerVisibility(true);
setSideStagePosition(STAGE_POSITION_BOTTOM_OR_RIGHT, false /* updateVisibility */);
setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateVisibility */);
setSplitsVisible(true);
addDividerBarToTransition(info, t, true /* show */);

View File

@@ -29,11 +29,13 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.io.PrintWriter;
@@ -44,6 +46,7 @@ import java.io.PrintWriter;
* They only serve to hold a collection of tasks and provide APIs like
* {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
* to perform operations in-sync with other containers.
*
* @see StageCoordinator
*/
class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@@ -58,23 +61,31 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
/** Callback interface for listening to changes in a split-screen stage. */
public interface StageListenerCallbacks {
void onRootTaskAppeared();
void onStatusChanged(boolean visible, boolean hasChildren);
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
void onRootTaskVanished();
void onNoLongerSupportMultiWindow();
}
private final StageListenerCallbacks mCallbacks;
private final SyncTransactionQueue mSyncQueue;
private final SurfaceSession mSurfaceSession;
protected ActivityManager.RunningTaskInfo mRootTaskInfo;
protected SurfaceControl mRootLeash;
protected SurfaceControl mDimLayer;
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) {
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession) {
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
@@ -94,6 +105,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mRootTaskInfo = taskInfo;
mCallbacks.onRootTaskAppeared();
sendStatusChanged();
mSyncQueue.runInSync(t -> mDimLayer =
SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession));
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
final int taskId = taskInfo.taskId;
mChildrenLeashes.put(taskId, leash);
@@ -146,6 +159,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
final int taskId = taskInfo.taskId;
if (mRootTaskInfo.taskId == taskId) {
mCallbacks.onRootTaskVanished();
mSyncQueue.runInSync(t -> t.remove(mDimLayer));
mRootTaskInfo = null;
} else if (mChildrenTaskInfo.contains(taskId)) {
mChildrenTaskInfo.remove(taskId);

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

View File

@@ -24,13 +24,13 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
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_UNDEFINED;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -206,7 +206,7 @@ public class DragAndDropPolicyTest {
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
}
@Test
@@ -218,12 +218,12 @@ public class DragAndDropPolicyTest {
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@Test
@@ -235,12 +235,12 @@ public class DragAndDropPolicyTest {
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@Test
@@ -252,7 +252,7 @@ public class DragAndDropPolicyTest {
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
}
@Test
@@ -264,7 +264,7 @@ public class DragAndDropPolicyTest {
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
}
@Test
@@ -277,13 +277,13 @@ public class DragAndDropPolicyTest {
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@Test
@@ -296,13 +296,13 @@ public class DragAndDropPolicyTest {
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@Test

View File

@@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityManager;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,7 +37,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
/** Tests for {@link MainStage} */
@SmallTest
@@ -47,14 +47,16 @@ public class MainStageTests {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ActivityManager.RunningTaskInfo mRootTaskInfo;
@Mock private SurfaceControl mRootLeash;
@Spy private WindowContainerTransaction mWct;
private WindowContainerTransaction mWct = new WindowContainerTransaction();
private SurfaceSession mSurfaceSession = new SurfaceSession();
private MainStage mMainStage;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue);
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
mSurfaceSession);
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
}

View File

@@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -52,13 +53,15 @@ public class SideStageTests {
@Mock private ActivityManager.RunningTaskInfo mRootTask;
@Mock private SurfaceControl mRootLeash;
@Spy private WindowContainerTransaction mWct;
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SideStage mSideStage;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build();
mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue);
mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
mSurfaceSession);
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}

View File

@@ -43,6 +43,7 @@ import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
@@ -80,6 +81,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private DisplayImeController mDisplayImeController;
@Mock private TransactionPool mTransactionPool;
@Mock private Transitions mTransitions;
@Mock private SurfaceSession mSurfaceSession;
private SplitLayout mSplitLayout;
private MainStage mMainStage;
private SideStage mSideStage;
@@ -98,10 +100,10 @@ public class SplitTransitionTests extends ShellTestCase {
doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
mSplitLayout = SplitTestUtils.createMockSplitLayout();
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue);
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue);
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,

View File

@@ -18,7 +18,7 @@ package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -73,7 +73,7 @@ public class StageCoordinatorTests extends ShellTestCase {
public void testMoveToSideStage() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
mStageCoordinator.moveToSideStage(task, STAGE_POSITION_BOTTOM_OR_RIGHT);
mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT);
verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class));
verify(mSideStage).addTask(eq(task), any(Rect.class),

View File

@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -38,6 +39,8 @@ 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;
@@ -52,6 +55,8 @@ public final class StageTaskListenerTests {
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
@Mock private SyncTransactionQueue mSyncQueue;
@Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
private SurfaceSession mSurfaceSession = new SurfaceSession();
private ActivityManager.RunningTaskInfo mRootTask;
private StageTaskListener mStageTaskListener;
@@ -62,12 +67,23 @@ public final class StageTaskListenerTests {
mTaskOrganizer,
DEFAULT_DISPLAY,
mCallbacks,
mSyncQueue);
mSyncQueue,
mSurfaceSession);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;
mStageTaskListener.onTaskAppeared(mRootTask, new SurfaceControl());
}
@Test
public void testInitsDimLayer() {
verify(mSyncQueue).runInSync(mRunnableCaptor.capture());
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
mRunnableCaptor.getValue().runWithTransaction(t);
t.apply();
assertThat(mStageTaskListener.mDimLayer).isNotNull();
}
@Test
public void testRootTaskAppeared() {
assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId);