diff options
| author | 2021-02-01 18:19:48 -0800 | |
|---|---|---|
| committer | 2021-02-03 08:27:33 -0800 | |
| commit | e3956ba87aa7913dccdfa4012817648e8ba89e66 (patch) | |
| tree | 47786877283b74e04160efe146df7105ee4024e5 | |
| parent | 7cf75f0b150f1a2b99a74671b578f2889a2e5e3b (diff) | |
Prep work for exposing split-screen APIs to Launcher
Bug: 179176511
Test: DragAndDropPolicyTest
Change-Id: Iff8e5df9df87779cf8a33430245fc5584928b33c
8 files changed, 340 insertions, 113 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index 52648d915f2c..fe97e24fac41 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -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; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 800150c0a93c..35dcdd5923a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -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"); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 7c1b9d813851..2c6809259459 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -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); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 27d3b81d41b5..18dd53b90ff4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -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 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index d571e7514542..176852b148fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -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(); } @@ -429,6 +471,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(); StageCoordinator.this.onStageRootTaskVanished(this); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 1aa7552c01eb..653299326cd0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -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; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 79bdaf43f171..25721066b713 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -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 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 168e0df35fe1..d2d18129d071 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -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), |