Merge "drag-n-drop support for main/side stage split-screen"

This commit is contained in:
Wale Ogunwale
2021-01-21 21:33:05 +00:00
committed by Android (Google) Code Review
13 changed files with 265 additions and 221 deletions

View File

@@ -100,7 +100,7 @@ public class ShellInitImpl {
mSplitScreenOptional.ifPresent(SplitScreen::onOrganizerRegistered);
// Bind the splitscreen impl to the drag drop controller
mDragAndDropController.initialize(mLegacySplitScreenOptional);
mDragAndDropController.initialize(mSplitScreenOptional);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.register(mShellTaskOrganizer);

View File

@@ -135,7 +135,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
final DividerSnapAlgorithm.SnapTarget snapTarget =
mSplitLayout.findSnapTarget(position, velocity);
mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */);
mSplitLayout.snapToTarget(position, snapTarget);
break;
}

View File

@@ -188,11 +188,11 @@ public class SplitLayout {
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
switch (snapTarget.flag) {
case FLAG_DISMISS_START:
mLayoutChangeListener.onSnappedToDismiss(false /* snappedToEnd */);
mLayoutChangeListener.onSnappedToDismiss(false /* bottomOrRight */);
mSplitWindowManager.setResizingSplits(false);
break;
case FLAG_DISMISS_END:
mLayoutChangeListener.onSnappedToDismiss(true /* snappedToEnd */);
mLayoutChangeListener.onSnappedToDismiss(true /* bottomOrRight */);
mSplitWindowManager.setResizingSplits(false);
break;
default:
@@ -207,9 +207,11 @@ public class SplitLayout {
/**
* Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity.
* If hardDismiss is set to {@code true}, it will be harder to reach dismiss target.
*/
public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity) {
return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity);
public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity,
boolean hardDismiss) {
return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
}
private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) {

View File

@@ -52,7 +52,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.util.Optional;
@@ -66,7 +66,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
private final Context mContext;
private final DisplayController mDisplayController;
private LegacySplitScreen mLegacySplitScreen;
private SplitScreen mSplitScreen;
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -76,8 +76,8 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
mDisplayController = displayController;
}
public void initialize(Optional<LegacySplitScreen> splitscreen) {
mLegacySplitScreen = splitscreen.orElse(null);
public void initialize(Optional<SplitScreen> splitscreen) {
mSplitScreen = splitscreen.orElse(null);
mDisplayController.addDisplayWindowListener(this);
}
@@ -104,7 +104,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
R.layout.global_drop_target, null);
rootView.setOnDragListener(this);
rootView.setVisibility(View.INVISIBLE);
DragLayout dragLayout = new DragLayout(context, mLegacySplitScreen);
DragLayout dragLayout = new DragLayout(context, mSplitScreen);
rootView.addView(dragLayout,
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
try {

View File

@@ -34,6 +34,8 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL
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.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -59,13 +61,12 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* The policy for handling drag and drop operations to shell.
@@ -77,22 +78,22 @@ public class DragAndDropPolicy {
private final Context mContext;
private final ActivityTaskManager mActivityTaskManager;
private final Starter mStarter;
private final LegacySplitScreen mLegacySplitScreen;
private final SplitScreen mSplitScreen;
private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
private DragSession mSession;
public DragAndDropPolicy(Context context, LegacySplitScreen legacySplitScreen) {
this(context, ActivityTaskManager.getInstance(), legacySplitScreen,
new DefaultStarter(context, legacySplitScreen));
public DragAndDropPolicy(Context context, SplitScreen splitScreen) {
this(context, ActivityTaskManager.getInstance(), splitScreen,
new DefaultStarter(context, splitScreen));
}
@VisibleForTesting
DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager,
LegacySplitScreen legacySplitScreen, Starter starter) {
SplitScreen splitScreen, Starter starter) {
mContext = context;
mActivityTaskManager = activityTaskManager;
mLegacySplitScreen = legacySplitScreen;
mSplitScreen = splitScreen;
mStarter = starter;
}
@@ -122,64 +123,54 @@ public class DragAndDropPolicy {
final int ih = h - insets.top - insets.bottom;
final int l = insets.left;
final int t = insets.top;
final boolean isVerticalSplit = mSession.isPhone && !mSession.displayLayout.isLandscape();
if (mSession.dragItemSupportsSplitscreen
&& mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD
&& mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN
&& mSession.runningTaskIsResizeable) {
// Allow splitting when there is a fullscreen standard activity running
if (isVerticalSplit) {
// TODO(b/169894807): For now, only allow splitting to the right/bottom until we
// have split pairs
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(l, t, l + iw, t + ih / 2),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
mTargets.add(new Target(TYPE_SPLIT_BOTTOM,
new Rect(l, t + ih / 2, l + iw, t + ih),
new Rect(l, t + ih / 2, l + iw, t + ih),
new Rect(0, h / 2, w, h)));
} else {
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(l, t, l + iw / 2, t + ih),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
mTargets.add(new Target(TYPE_SPLIT_RIGHT,
new Rect(l + iw / 2, t, l + iw, t + ih),
new Rect(l + iw / 2, t, l + iw, t + ih),
new Rect(w / 2, 0, w, h)));
}
} else if (mSession.dragItemSupportsSplitscreen
&& mLegacySplitScreen != null
&& mLegacySplitScreen.isDividerVisible()) {
final Rect displayRegion = new Rect(l, t, l + iw, t + ih);
final Rect fullscreenDrawRegion = new Rect(displayRegion);
final Rect fullscreenHitRegion = new Rect(displayRegion);
final boolean inLandscape = mSession.displayLayout.isLandscape();
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
// We allow splitting if we are already in split-screen or the running task is a standard
// task in fullscreen mode.
final boolean allowSplit = inSplitScreen
|| (mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD
&& mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN);
if (allowSplit) {
// Already split, allow replacing existing split task
// TODO(b/169894807): For now, only allow replacing the non-primary task until we have
// split pairs
final Rect secondarySplitRawBounds =
mLegacySplitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds();
final Rect secondarySplitBounds = new Rect(secondarySplitRawBounds);
secondarySplitBounds.intersect(new Rect(l, t, l + iw, t + ih));
if (isVerticalSplit) {
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(l, t, l + iw, secondarySplitRawBounds.top),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, secondarySplitRawBounds.top)));
final Rect topOrLeftBounds = new Rect();
final Rect bottomOrRightBounds = new Rect();
mSplitScreen.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
topOrLeftBounds.intersect(displayRegion);
bottomOrRightBounds.intersect(displayRegion);
if (inLandscape) {
final Rect leftHitRegion = new Rect();
final Rect leftDrawRegion = topOrLeftBounds;
final Rect rightHitRegion = new Rect();
final Rect rightDrawRegion = bottomOrRightBounds;
displayRegion.splitVertically(leftHitRegion, fullscreenHitRegion, rightHitRegion);
mTargets.add(
new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
} else {
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(l, t, secondarySplitRawBounds.left, t + ih),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
final Rect topHitRegion = new Rect();
final Rect topDrawRegion = topOrLeftBounds;
final Rect bottomHitRegion = new Rect();
final Rect bottomDrawRegion = bottomOrRightBounds;
displayRegion.splitHorizontally(
topHitRegion, fullscreenHitRegion, bottomHitRegion);
mTargets.add(
new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
}
mTargets.add(new Target(isVerticalSplit ? TYPE_SPLIT_BOTTOM : TYPE_SPLIT_RIGHT,
new Rect(secondarySplitBounds),
new Rect(secondarySplitBounds),
new Rect(secondarySplitBounds)));
} else {
// Otherwise only show the fullscreen target
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(l, t, l + iw, t + ih),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
// Split-screen not allowed, so only show the fullscreen target
mTargets.add(new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
}
return mTargets;
}
@@ -208,58 +199,34 @@ public class DragAndDropPolicy {
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final Intent dragData = mSession.dragData;
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
final boolean leftOrTop = target.type == TYPE_SPLIT_TOP || target.type == TYPE_SPLIT_LEFT;
final Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
: new Bundle();
boolean deferAppLaunchUntilSplit = false;
if (target.type == TYPE_FULLSCREEN) {
if (mLegacySplitScreen != null && mLegacySplitScreen.isDividerVisible()) {
// If in split, remove split and launch fullscreen
mStarter.exitSplitScreen(mSession.runningTaskId);
} else {
// Not in split, fall through to launch
}
} else {
if (mLegacySplitScreen != null && mLegacySplitScreen.isDividerVisible()) {
// Split is already visible, just replace the task
// TODO(b/169894807): Since we only allow replacing the non-primary target above
// just fall through and start the activity
} else {
// Not in split, enter split now
mStarter.enterSplitScreen(mSession.runningTaskId,
target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP);
deferAppLaunchUntilSplit = true;
// Exit split stages if needed
mStarter.exitSplitScreen();
} else if (mSplitScreen != null) {
// Update launch options for the split side we are targeting.
final int position = leftOrTop
? SIDE_STAGE_POSITION_TOP_OR_LEFT : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
if (!inSplitScreen) {
// Update the side stage position to match where we want to launch.
mSplitScreen.setSideStagePosition(position);
}
mSplitScreen.updateActivityOptions(opts, position);
}
final Runnable startAppRunnable = () -> {
Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
: null;
if (isTask) {
mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
} else if (isShortcut) {
mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME),
dragData.getStringExtra(EXTRA_SHORTCUT_ID),
opts, dragData.getParcelableExtra(EXTRA_USER));
} else {
mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
}
};
if (deferAppLaunchUntilSplit) {
// TODO(b/169894807): The enterSplitScreen() call above will trigger the current task
// into split, and we should wait for home and other tasks to be moved to
// split-secondary before trying to launch the new secondary task. This can be removed
// once we have app-pairs.
mLegacySplitScreen.registerInSplitScreenListener(new Consumer<Boolean>() {
@Override
public void accept(Boolean inSplit) {
if (inSplit) {
startAppRunnable.run();
mLegacySplitScreen.unregisterInSplitScreenListener(this);
}
}
});
if (isTask) {
mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
} else if (isShortcut) {
mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME),
dragData.getStringExtra(EXTRA_SHORTCUT_ID),
opts, dragData.getParcelableExtra(EXTRA_USER));
} else {
startAppRunnable.run();
mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
}
}
@@ -323,7 +290,7 @@ public class DragAndDropPolicy {
UserHandle user);
void startIntent(PendingIntent intent, Bundle activityOptions);
void enterSplitScreen(int taskId, boolean leftOrTop);
void exitSplitScreen(int taskId);
void exitSplitScreen();
}
/**
@@ -332,17 +299,17 @@ public class DragAndDropPolicy {
*/
private static class DefaultStarter implements Starter {
private final Context mContext;
private final LegacySplitScreen mLegacySplitScreen;
private final SplitScreen mSplitScreen;
public DefaultStarter(Context context, LegacySplitScreen legacySplitScreen) {
public DefaultStarter(Context context, SplitScreen splitScreen) {
mContext = context;
mLegacySplitScreen = legacySplitScreen;
mSplitScreen = splitScreen;
}
@Override
public void startTask(int taskId, Bundle activityOptions) {
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, null);
ActivityTaskManager.getService().startActivityFromRecents(taskId, activityOptions);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
@@ -372,12 +339,14 @@ public class DragAndDropPolicy {
@Override
public void enterSplitScreen(int taskId, boolean leftOrTop) {
mLegacySplitScreen.splitPrimaryTask();
mSplitScreen.moveToSideStage(taskId,
leftOrTop ? SIDE_STAGE_POSITION_TOP_OR_LEFT
: SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
}
@Override
public void exitSplitScreen(int taskId) {
mLegacySplitScreen.dismissSplitToPrimaryTask();
public void exitSplitScreen() {
mSplitScreen.exitSplitScreen();
}
}
@@ -406,19 +375,16 @@ public class DragAndDropPolicy {
final Rect hitRegion;
// The approximate visual region for where the task will start
final Rect drawRegion;
// The
final Rect dropTargetBounds;
public Target(@Type int t, Rect hit, Rect draw, Rect drop) {
public Target(@Type int t, Rect hit, Rect draw) {
type = t;
hitRegion = hit;
drawRegion = draw;
dropTargetBounds = drop;
}
@Override
public String toString() {
return "Target {hit=" + hitRegion + " drop=" + dropTargetBounds + "}";
return "Target {hit=" + hitRegion + " draw=" + drawRegion + "}";
}
}
}

View File

@@ -42,7 +42,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.util.ArrayList;
@@ -61,7 +61,7 @@ public class DragLayout extends View {
private boolean mIsShowing;
private boolean mHasDropped;
public DragLayout(Context context, LegacySplitScreen splitscreen) {
public DragLayout(Context context, SplitScreen splitscreen) {
super(context);
mPolicy = new DragAndDropPolicy(context, splitscreen);
mDisplayMargin = context.getResources().getDimensionPixelSize(

View File

@@ -38,12 +38,6 @@ class MainStage extends StageTaskListener {
private boolean mIsActive = false;
private static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
private static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
private static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) {
super(taskOrganizer, displayId, callbacks, syncQueue);
@@ -92,7 +86,7 @@ class MainStage extends StageTaskListener {
null /* newParent */,
CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
CONTROLLED_ACTIVITY_TYPES,
true /* onTop */)
false /* onTop */)
.reorder(rootToken, false /* onTop */);
}

View File

@@ -16,6 +16,8 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.window.WindowContainerToken;
@@ -48,6 +50,17 @@ class SideStage extends StageTaskListener {
.reorder(rootToken, true);
}
boolean removeAllTasks(WindowContainerTransaction wct) {
if (mChildrenTaskInfo.size() == 0) return false;
wct.reparentTasks(
mRootTaskInfo.token,
null /* newParent */,
CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
CONTROLLED_ACTIVITY_TYPES,
false /* onTop */);
return true;
}
boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
if (task == null) return false;
@@ -57,4 +70,12 @@ class SideStage extends StageTaskListener {
.reparent(task.token, newParent, false /* onTop */);
return true;
}
int getTopVisibleTaskId() {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.valueAt(i);
if (task.isVisible) return task.taskId;
}
return INVALID_TASK_ID;
}
}

View File

@@ -18,6 +18,8 @@ package com.android.wm.shell.splitscreen;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -61,6 +63,12 @@ public interface SplitScreen {
void setSideStagePosition(@SideStagePosition int sideStagePosition);
/** Hides the side-stage if it is currently visible. */
void setSideStageVisibility(boolean visible);
/** Removes the split-screen stages. */
void exitSplitScreen();
/** Gets the stage bounds. */
void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
/** Updates the launch activity options for the split position we want to launch it in. */
void updateActivityOptions(Bundle opts, @SideStagePosition int position);
/** Dumps current status of split-screen. */
void dump(@NonNull PrintWriter pw, String prefix);
/** Called when the shell organizer has been registered. */

View File

@@ -20,6 +20,8 @@ import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -69,7 +71,10 @@ public class SplitScreenController implements SplitScreen {
@Override
public boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
return task != null && moveToSideStage(task, sideStagePosition);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
}
return moveToSideStage(task, sideStagePosition);
}
@Override
@@ -93,6 +98,21 @@ public class SplitScreenController implements SplitScreen {
mStageCoordinator.setSideStageVisibility(visible);
}
@Override
public void exitSplitScreen() {
mStageCoordinator.exitSplitScreen();
}
@Override
public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
}
@Override
public void updateActivityOptions(Bundle opts, @SideStagePosition int position) {
mStageCoordinator.updateActivityOptions(opts, position);
}
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
pw.println(prefix + TAG);

View File

@@ -16,6 +16,8 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -25,6 +27,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_T
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
@@ -142,6 +145,31 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
mTaskOrganizer.applyTransaction(wct);
}
void exitSplitScreen() {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStage.removeAllTasks(wct);
mMainStage.deactivate(wct);
mTaskOrganizer.applyTransaction(wct);
}
void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
outTopOrLeftBounds.set(mSplitLayout.getBounds1());
outBottomOrRightBounds.set(mSplitLayout.getBounds2());
}
void updateActivityOptions(Bundle opts, @SplitScreen.SideStagePosition int position) {
final StageTaskListener stage = position == mSideStagePosition ? mSideStage : mMainStage;
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
if (!mMainStage.isActive()) {
// Activate the main stage in anticipation of an app launch.
final WindowContainerTransaction wct = new WindowContainerTransaction();
mMainStage.activate(getMainStageBounds(), wct);
mSideStage.setBounds(getSideStageBounds(), wct);
mTaskOrganizer.applyTransaction(wct);
}
}
private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -175,6 +203,12 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
}
if (!mainStageVisible && !sideStageVisible) {
// Exit split-screen if both stage are not visible.
// TODO: This is only a temporary request from UX and is likely to be removed soon...
exitSplitScreen();
}
if (mainStageVisible) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (sideStageVisible) {
@@ -252,10 +286,27 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
@Override
public void onSnappedToDismiss(boolean snappedToEnd) {
// TODO: What to do...what to do...
public void onSnappedToDismiss(boolean bottomOrRight) {
if (mSideStagePosition == SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT && bottomOrRight) {
// Main stage was fully expanded...Just side side-stage.
setSideStageVisibility(false);
} else {
// Side stage was fully expanded...Move its top task to the main stage
// and hide side-stage.
// TODO: Would UX prefer the side-stage go into fullscreen mode here?
final int taskId = mSideStage.getTopVisibleTaskId();
if (taskId == INVALID_TASK_ID) {
throw new IllegalStateException("Side stage doesn't have visible task? "
+ mSideStage);
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStage.removeTask(taskId, mMainStage.mRootTaskInfo.getToken(), wct);
mSideStage.setVisibility(false, wct);
mTaskOrganizer.applyTransaction(wct);
}
// Reset divider position.
mSplitLayout.resetDividerPosition();
onBoundsChanged(mSplitLayout);
}
@Override

View File

@@ -16,7 +16,10 @@
package com.android.wm.shell.splitscreen;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.annotation.CallSuper;
import android.app.ActivityManager;
@@ -45,6 +48,12 @@ import java.io.PrintWriter;
class StageTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = StageTaskListener.class.getSimpleName();
protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
protected static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
/** Callback interface for listening to changes in a split-screen stage. */
public interface StageListenerCallbacks {
void onRootTaskAppeared();
@@ -69,7 +78,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
if (!taskInfo.hasParentTask()) {
if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
mCallbacks.onRootTaskAppeared();
@@ -78,7 +87,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
} else {
throw new IllegalArgumentException("Unknown task: " + taskInfo);
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
sendStatusChanged();
}
@@ -93,7 +103,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
updateChildTaskSurface(
taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
} else {
throw new IllegalArgumentException("Unknown task: " + taskInfo);
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
sendStatusChanged();
}
@@ -110,7 +121,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mChildrenLeashes.remove(taskId);
sendStatusChanged();
} else {
throw new IllegalArgumentException("Unknown task: " + taskInfo);
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
}
@@ -143,6 +155,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
}
}

View File

@@ -26,7 +26,9 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
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 junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -35,7 +37,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -52,7 +53,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.RemoteException;
import android.view.DisplayInfo;
@@ -61,20 +61,17 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
import com.android.wm.shell.legacysplitscreen.DividerView;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.function.Consumer;
/**
* Tests for the drag and drop policy.
@@ -90,12 +87,13 @@ public class DragAndDropPolicyTest {
private ActivityTaskManager mActivityTaskManager;
@Mock
private LegacySplitScreen mLegacySplitScreen;
private SplitScreen mSplitScreen;
@Mock
private DragAndDropPolicy.Starter mStarter;
private DisplayLayout mDisplayLayout;
private DisplayLayout mLandscapeDisplayLayout;
private DisplayLayout mPortraitDisplayLayout;
private Insets mInsets;
private DragAndDropPolicy mPolicy;
@@ -116,25 +114,19 @@ public class DragAndDropPolicyTest {
Resources res = mock(Resources.class);
Configuration config = new Configuration();
doReturn(config).when(res).getConfiguration();
doReturn(res).when(mContext).getResources();
DisplayInfo info = new DisplayInfo();
info.logicalWidth = 100;
info.logicalWidth = 200;
info.logicalHeight = 100;
mDisplayLayout = new DisplayLayout(info, res, false, false);
mLandscapeDisplayLayout = new DisplayLayout(info, res, false, false);
DisplayInfo info2 = new DisplayInfo();
info.logicalWidth = 100;
info.logicalHeight = 200;
mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
mInsets = Insets.of(0, 0, 0, 0);
DividerView divider = mock(DividerView.class);
doReturn(divider).when(mLegacySplitScreen).getDividerView();
doReturn(new Rect(50, 0, 100, 100)).when(divider)
.getNonMinimizedSplitScreenSecondaryBounds();
doAnswer((Answer<Void>) invocation -> {
Consumer<Boolean> callback = invocation.getArgument(0);
callback.accept(true);
return null;
}).when(mLegacySplitScreen).registerInSplitScreenListener(any());
mPolicy = new DragAndDropPolicy(
mContext, mActivityTaskManager, mLegacySplitScreen, mStarter);
mContext, mActivityTaskManager, mSplitScreen, mStarter);
mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -149,7 +141,6 @@ public class DragAndDropPolicyTest {
mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
ACTIVITY_TYPE_STANDARD);
setIsPhone(false);
setInSplitScreen(false);
setRunningTask(mFullscreenAppTask);
}
@@ -199,22 +190,14 @@ public class DragAndDropPolicyTest {
: ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
}
private void setIsPhone(boolean isPhone) {
Resources res = mock(Resources.class);
Configuration config = mock(Configuration.class);
config.smallestScreenWidthDp = isPhone ? 400 : 800;
doReturn(config).when(res).getConfiguration();
doReturn(res).when(mContext).getResources();
}
private void setInSplitScreen(boolean inSplitscreen) {
doReturn(inSplitscreen).when(mLegacySplitScreen).isDividerVisible();
doReturn(inSplitscreen).when(mSplitScreen).isSplitScreenVisible();
}
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() throws RemoteException {
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
setRunningTask(mHomeTask);
mPolicy.start(mDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
@@ -223,77 +206,66 @@ public class DragAndDropPolicyTest {
}
@Test
public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets()
throws RemoteException {
public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets() {
setRunningTask(mFullscreenAppTask);
mPolicy.start(mDisplayLayout, mActivityClipData);
// TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
// pairs
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_RIGHT);
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).exitSplitScreen();
verify(mStarter).startIntent(any(), any());
reset(mStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
verify(mStarter).enterSplitScreen(anyInt(), eq(false));
verify(mStarter).startIntent(any(), any());
}
@Test
public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets()
throws RemoteException {
setIsPhone(true);
public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets() {
setRunningTask(mFullscreenAppTask);
mPolicy.start(mDisplayLayout, mActivityClipData);
// TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
// pairs
mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_BOTTOM);
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).exitSplitScreen();
verify(mStarter).startIntent(any(), any());
reset(mStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
verify(mStarter).enterSplitScreen(anyInt(), eq(false));
verify(mStarter).startIntent(any(), any());
}
@Test
public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets()
throws RemoteException {
public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets() {
setRunningTask(mNonResizeableFullscreenAppTask);
mPolicy.start(mDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
}
@Test
public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets()
throws RemoteException {
public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets() {
setRunningTask(mFullscreenAppTask);
mPolicy.start(mDisplayLayout, mNonResizeableActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mNonResizeableActivityClipData);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
}
@Test
public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() throws RemoteException {
public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
mPolicy.start(mDisplayLayout, mActivityClipData);
// TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
// pairs
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_RIGHT);
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
@@ -305,16 +277,12 @@ public class DragAndDropPolicyTest {
}
@Test
public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets()
throws RemoteException {
setIsPhone(true);
public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
mPolicy.start(mDisplayLayout, mActivityClipData);
// TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
// pairs
mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_BOTTOM);
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
@@ -326,9 +294,9 @@ public class DragAndDropPolicyTest {
}
@Test
public void testTargetHitRects() throws RemoteException {
public void testTargetHitRects() {
setRunningTask(mFullscreenAppTask);
mPolicy.start(mDisplayLayout, mActivityClipData);
mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
for (Target t : targets) {
assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t);