Prep work for exposing split-screen APIs to Launcher

Bug: 179176511
Test: DragAndDropPolicyTest
Change-Id: Iff8e5df9df87779cf8a33430245fc5584928b33c
This commit is contained in:
Wale Ogunwale
2021-02-01 18:19:48 -08:00
parent 7cf75f0b15
commit e3956ba87a
8 changed files with 339 additions and 112 deletions

View File

@@ -16,7 +16,7 @@
package com.android.wm.shell;
import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.common.ShellExecutor;
@@ -155,7 +155,7 @@ public final class ShellCommandHandlerImpl {
}
final int taskId = new Integer(args[2]);
final int sideStagePosition = args.length > 3
? new Integer(args[3]) : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
? new Integer(args[3]) : STAGE_POSITION_BOTTOM_OR_RIGHT;
mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
return true;
}

View File

@@ -34,8 +34,11 @@ 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 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;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -84,8 +87,7 @@ public class DragAndDropPolicy {
private DragSession mSession;
public DragAndDropPolicy(Context context, SplitScreen splitScreen) {
this(context, ActivityTaskManager.getInstance(), splitScreen,
new DefaultStarter(context, splitScreen));
this(context, ActivityTaskManager.getInstance(), splitScreen, new DefaultStarter(context));
}
@VisibleForTesting
@@ -94,7 +96,7 @@ public class DragAndDropPolicy {
mContext = context;
mActivityTaskManager = activityTaskManager;
mSplitScreen = splitScreen;
mStarter = starter;
mStarter = mSplitScreen != null ? mSplitScreen : starter;
}
/**
@@ -195,39 +197,23 @@ public class DragAndDropPolicy {
return;
}
final ClipDescription description = data.getDescription();
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();
if (target.type == TYPE_FULLSCREEN) {
// Exit split stages if needed
mStarter.exitSplitScreen();
} else if (mSplitScreen != null) {
@SplitScreen.StageType int stage = STAGE_TYPE_UNDEFINED;
@SplitScreen.StagePosition int position = STAGE_POSITION_UNDEFINED;
if (target.type != TYPE_FULLSCREEN && 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;
position = leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
if (!inSplitScreen) {
// Update the side stage position to match where we want to launch.
mSplitScreen.setSideStagePosition(position);
// Launch in the side stage if we are not in split-screen already.
stage = STAGE_TYPE_SIDE;
}
mSplitScreen.updateActivityOptions(opts, position);
}
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);
}
final ClipDescription description = data.getDescription();
final Intent dragData = mSession.dragData;
mStarter.startClipDescription(description, dragData, stage, position);
}
/**
@@ -247,7 +233,6 @@ public class DragAndDropPolicy {
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean runningTaskIsResizeable;
boolean dragItemSupportsSplitscreen;
boolean isPhone;
DragSession(Context context, ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
@@ -275,7 +260,6 @@ public class DragAndDropPolicy {
final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
dragItemSupportsSplitscreen = info == null
|| ActivityInfo.isResizeableMode(info.resizeMode);
isPhone = mContext.getResources().getConfiguration().smallestScreenWidthDp < 600;
dragData = mInitialDragData.getItemAt(0).getIntent();
}
}
@@ -284,11 +268,33 @@ public class DragAndDropPolicy {
* Interface for actually committing the task launches.
*/
@VisibleForTesting
interface Starter {
void startTask(int taskId, Bundle activityOptions);
void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
UserHandle user);
void startIntent(PendingIntent intent, Bundle activityOptions);
public interface Starter {
default void startClipDescription(ClipDescription description, Intent intent,
@SplitScreen.StageType int stage, @SplitScreen.StagePosition 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)
? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) : new Bundle();
if (isTask) {
final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
startTask(taskId, stage, position, opts);
} else if (isShortcut) {
final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
startShortcut(packageName, id, stage, position, opts, user);
} else {
startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), stage, position, opts);
}
}
void startTask(int taskId, @SplitScreen.StageType int stage,
@SplitScreen.StagePosition int position, @Nullable Bundle options);
void startShortcut(String packageName, String shortcutId,
@SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
@Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent, @SplitScreen.StageType int stage,
@SplitScreen.StagePosition int position, @Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
void exitSplitScreen();
}
@@ -299,39 +305,39 @@ public class DragAndDropPolicy {
*/
private static class DefaultStarter implements Starter {
private final Context mContext;
private final SplitScreen mSplitScreen;
public DefaultStarter(Context context, SplitScreen splitScreen) {
public DefaultStarter(Context context) {
mContext = context;
mSplitScreen = splitScreen;
}
@Override
public void startTask(int taskId, Bundle activityOptions) {
public void startTask(int taskId, int stage, int position,
@Nullable Bundle options) {
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, activityOptions);
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
}
@Override
public void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
UserHandle user) {
public void startShortcut(String packageName, String shortcutId, int stage, int position,
@Nullable Bundle options, UserHandle user) {
try {
LauncherApps launcherApps =
mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
activityOptions, user);
options, user);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
}
@Override
public void startIntent(PendingIntent intent, Bundle activityOptions) {
public void startIntent(PendingIntent intent, int stage, int position,
@Nullable Bundle options) {
try {
intent.send(null, 0, null, null, null, null, activityOptions);
intent.send(null, 0, null, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
}
@@ -339,14 +345,12 @@ public class DragAndDropPolicy {
@Override
public void enterSplitScreen(int taskId, boolean leftOrTop) {
mSplitScreen.moveToSideStage(taskId,
leftOrTop ? SIDE_STAGE_POSITION_TOP_OR_LEFT
: SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
throw new UnsupportedOperationException("enterSplitScreen not implemented by starter");
}
@Override
public void exitSplitScreen() {
mSplitScreen.exitSplitScreen();
throw new UnsupportedOperationException("exitSplitScreen not implemented by starter");
}
}

View File

@@ -18,12 +18,16 @@ package com.android.wm.shell.splitscreen;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import java.io.PrintWriter;
@@ -31,46 +35,93 @@ import java.io.PrintWriter;
* Interface to engage split-screen feature.
*/
@ExternalThread
public interface SplitScreen {
public interface SplitScreen extends DragAndDropPolicy.Starter {
/**
* Specifies that the side-stage is positioned at the top half of the screen if
* 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 SIDE_STAGE_POSITION_TOP_OR_LEFT = 0;
int STAGE_POSITION_TOP_OR_LEFT = 0;
/**
* Specifies that the side-stage is positioned at the bottom half of the screen if
* 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 SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
@IntDef(prefix = { "SIDE_STAGE_POSITION_" }, value = {
SIDE_STAGE_POSITION_TOP_OR_LEFT,
SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT
@IntDef(prefix = { "STAGE_POSITION_" }, value = {
STAGE_POSITION_UNDEFINED,
STAGE_POSITION_TOP_OR_LEFT,
STAGE_POSITION_BOTTOM_OR_RIGHT
})
@interface SideStagePosition {}
@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.
*/
int STAGE_TYPE_UNDEFINED = -1;
/**
* The main stage type.
* @see MainStage
*/
int STAGE_TYPE_MAIN = 0;
/**
* The side stage type.
* @see SideStage
*/
int STAGE_TYPE_SIDE = 1;
@IntDef(prefix = { "STAGE_TYPE_" }, value = {
STAGE_TYPE_UNDEFINED,
STAGE_TYPE_MAIN,
STAGE_TYPE_SIDE
})
@interface StageType {}
/** Callback interface for listening to changes in a split-screen stage. */
interface SplitScreenListener {
void onStagePositionChanged(@StageType int stage, @StagePosition int position);
void onTaskStageChanged(int taskId, @StageType int stage);
}
/** @return {@code true} if split-screen is currently visible. */
boolean isSplitScreenVisible();
/** Moves a task in the side-stage of split-screen. */
boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition);
boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition);
/** Moves a task in the side-stage of split-screen. */
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SideStagePosition int sideStagePosition);
@StagePosition int sideStagePosition);
/** Removes a task from the side-stage of split-screen. */
boolean removeFromSideStage(int taskId);
/** Sets the position of the side-stage. */
void setSideStagePosition(@SideStagePosition int sideStagePosition);
void setSideStagePosition(@StagePosition int sideStagePosition);
/** Hides the side-stage if it is currently visible. */
void setSideStageVisibility(boolean visible);
default void enterSplitScreen(int taskId, boolean leftOrTop) {
moveToSideStage(taskId,
leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT);
}
/** 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. */
void onOrganizerRegistered();
void registerSplitScreenListener(SplitScreenListener listener);
void unregisterSplitScreenListener(SplitScreenListener listener);
void startTask(int taskId,
@StageType int stage, @StagePosition int position, @Nullable Bundle options);
void startShortcut(String packageName, String shortcutId, @StageType int stage,
@StagePosition int position, @Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent,
@StageType int stage, @StagePosition int position, @Nullable Bundle options);
}

View File

@@ -19,11 +19,19 @@ package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -69,7 +77,7 @@ public class SplitScreenController implements SplitScreen {
}
@Override
public boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition) {
public boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
@@ -79,7 +87,7 @@ public class SplitScreenController implements SplitScreen {
@Override
public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SideStagePosition int sideStagePosition) {
@StagePosition int sideStagePosition) {
return mStageCoordinator.moveToSideStage(task, sideStagePosition);
}
@@ -89,7 +97,7 @@ public class SplitScreenController implements SplitScreen {
}
@Override
public void setSideStagePosition(@SideStagePosition int sideStagePosition) {
public void setSideStagePosition(@StagePosition int sideStagePosition) {
mStageCoordinator.setSideStagePosition(sideStagePosition);
}
@@ -109,8 +117,103 @@ public class SplitScreenController implements SplitScreen {
}
@Override
public void updateActivityOptions(Bundle opts, @SideStagePosition int position) {
mStageCoordinator.updateActivityOptions(opts, position);
public void registerSplitScreenListener(SplitScreenListener listener) {
mStageCoordinator.registerSplitScreenListener(listener);
}
@Override
public void unregisterSplitScreenListener(SplitScreenListener listener) {
mStageCoordinator.unregisterSplitScreenListener(listener);
}
@Override
public void startTask(int taskId,
@StageType int stage, @StagePosition int position, @Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
}
@Override
public void startShortcut(String packageName, String shortcutId, @StageType int stage,
@StagePosition int position, @Nullable Bundle options, UserHandle user) {
options = resolveStartStage(stage, position, options);
try {
LauncherApps launcherApps =
mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
options, user);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
}
@Override
public void startIntent(PendingIntent intent,
@StageType int stage, @StagePosition int position, @Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
try {
intent.send(null, 0, null, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
}
}
private Bundle resolveStartStage(@StageType int stage, @StagePosition 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 == mStageCoordinator.getSideStagePosition()) {
options = resolveStartStage(STAGE_TYPE_SIDE, position, options);
} else {
options = resolveStartStage(STAGE_TYPE_MAIN, position, options);
}
} else {
// Exit split-screen and launch fullscreen since stage wasn't specified.
mStageCoordinator.exitSplitScreen();
}
break;
}
case STAGE_TYPE_SIDE: {
if (position != STAGE_POSITION_UNDEFINED) {
mStageCoordinator.setSideStagePosition(position);
} else {
position = mStageCoordinator.getSideStagePosition();
}
if (options == null) {
options = new Bundle();
}
mStageCoordinator.updateActivityOptions(options, position);
break;
}
case STAGE_TYPE_MAIN: {
if (position != STAGE_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;
mStageCoordinator.setSideStagePosition(sideStagePosition);
} else {
position = mStageCoordinator.getMainStagePosition();
}
if (options == null) {
options = new Bundle();
}
mStageCoordinator.updateActivityOptions(options, position);
break;
}
default:
throw new IllegalArgumentException("Unknown stage=" + stage);
}
return options;
}
@Override

View File

@@ -17,12 +17,14 @@
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;
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 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_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.app.ActivityManager;
import android.content.Context;
@@ -41,6 +43,8 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitLayout;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -64,8 +68,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
private @SplitScreen.SideStagePosition int mSideStagePosition =
SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
private @SplitScreen.StagePosition int mSideStagePosition = STAGE_POSITION_BOTTOM_OR_RIGHT;
private final int mDisplayId;
private SplitLayout mSplitLayout;
@@ -75,6 +78,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
private final ShellTaskOrganizer mTaskOrganizer;
private DisplayAreaInfo mDisplayAreaInfo;
private final Context mContext;
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) {
@@ -107,7 +111,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SplitScreen.SideStagePosition int sideStagePosition) {
@SplitScreen.StagePosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStagePosition = sideStagePosition;
mMainStage.activate(getMainStageBounds(), wct);
@@ -130,7 +134,16 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
return result;
}
void setSideStagePosition(@SplitScreen.SideStagePosition int sideStagePosition) {
@SplitScreen.StagePosition 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;
}
void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
mSideStagePosition = sideStagePosition;
if (mSideStageListener.mVisible) {
onStageVisibilityChanged(mSideStageListener);
@@ -163,7 +176,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
outBottomOrRightBounds.set(mSplitLayout.getBounds2());
}
void updateActivityOptions(Bundle opts, @SplitScreen.SideStagePosition int position) {
void updateActivityOptions(Bundle opts, @SplitScreen.StagePosition int position) {
final StageTaskListener stage = position == mSideStagePosition ? mSideStage : mMainStage;
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
@@ -176,6 +189,35 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
}
void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
if (mListeners.contains(listener)) return;
mListeners.add(listener);
listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
}
void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
mListeners.remove(listener);
}
private void onStageChildTaskStatusChanged(
StageListenerImpl stageListener, int taskId, boolean present) {
int stage;
if (present) {
stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
} else {
// No longer on any stage
stage = STAGE_TYPE_UNDEFINED;
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onTaskStageChanged(taskId, stage);
}
}
private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -299,7 +341,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
@Override
public void onSnappedToDismiss(boolean bottomOrRight) {
final boolean mainStageToTop = bottomOrRight
&& mSideStagePosition == SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
&& mSideStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
exitSplitScreen(mainStageToTop ? mMainStage : mSideStage);
}
@@ -326,8 +368,8 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
@Override
public void onDoubleTappedDivider() {
setSideStagePosition(mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
? SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT : SIDE_STAGE_POSITION_TOP_OR_LEFT);
setSideStagePosition(mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT);
}
@Override
@@ -380,12 +422,12 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
private Rect getSideStageBounds() {
return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
}
private Rect getMainStageBounds() {
return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
}
@@ -428,6 +470,11 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener,
}
}
@Override
public void onChildTaskStatusChanged(int taskId, boolean present) {
StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present);
}
@Override
public void onRootTaskVanished() {
reset();

View File

@@ -58,6 +58,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
public interface StageListenerCallbacks {
void onRootTaskAppeared();
void onStatusChanged(boolean visible, boolean hasChildren);
void onChildTaskStatusChanged(int taskId, boolean present);
void onRootTaskVanished();
}
private final StageListenerCallbacks mCallbacks;
@@ -83,9 +84,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mRootTaskInfo = taskInfo;
mCallbacks.onRootTaskAppeared();
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
mChildrenLeashes.put(taskInfo.taskId, leash);
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
final int taskId = taskInfo.taskId;
mChildrenLeashes.put(taskId, leash);
mChildrenTaskInfo.put(taskId, taskInfo);
updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
mCallbacks.onChildTaskStatusChanged(taskId, true /* present */);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -120,6 +123,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
sendStatusChanged();
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -134,6 +138,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
wct.reorder(mRootTaskInfo.token, visible /* onTop */);
}
void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
@SplitScreen.StageType int stage) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
listener.onTaskStageChanged(mChildrenTaskInfo.keyAt(i), stage);
}
}
private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl leash, boolean firstAppeared) {
final Point taskPositionInParent = taskInfo.positionInParent;

View File

@@ -29,6 +29,10 @@ 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.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;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -86,11 +90,9 @@ public class DragAndDropPolicyTest {
@Mock
private ActivityTaskManager mActivityTaskManager;
// Both the split-screen and start interface.
@Mock
private SplitScreen mSplitScreen;
@Mock
private DragAndDropPolicy.Starter mStarter;
private SplitScreen mSplitScreenStarter;
private DisplayLayout mLandscapeDisplayLayout;
private DisplayLayout mPortraitDisplayLayout;
@@ -126,7 +128,7 @@ public class DragAndDropPolicyTest {
mInsets = Insets.of(0, 0, 0, 0);
mPolicy = new DragAndDropPolicy(
mContext, mActivityTaskManager, mSplitScreen, mStarter);
mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter);
mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -191,7 +193,7 @@ public class DragAndDropPolicyTest {
}
private void setInSplitScreen(boolean inSplitscreen) {
doReturn(inSplitscreen).when(mSplitScreen).isSplitScreenVisible();
doReturn(inSplitscreen).when(mSplitScreenStarter).isSplitScreenVisible();
}
@Test
@@ -202,7 +204,8 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
}
@Test
@@ -213,12 +216,13 @@ public class DragAndDropPolicyTest {
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);
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
verify(mStarter).startIntent(any(), any());
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test
@@ -229,12 +233,13 @@ public class DragAndDropPolicyTest {
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);
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
verify(mStarter).startIntent(any(), any());
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test
@@ -245,7 +250,8 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
}
@Test
@@ -256,7 +262,8 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
}
@Test
@@ -268,12 +275,14 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
reset(mStarter);
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
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(mStarter).startIntent(any(), any());
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test
@@ -285,12 +294,14 @@ public class DragAndDropPolicyTest {
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
verify(mStarter).startIntent(any(), any());
reset(mStarter);
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
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(mStarter).startIntent(any(), any());
verify(mSplitScreenStarter).startClipDescription(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
}
@Test

View File

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