From db4c59f87b68d98cc406d16c00155c279307aa19 Mon Sep 17 00:00:00 2001 From: Jeff Chang Date: Fri, 16 Dec 2022 13:32:57 +0800 Subject: [PATCH] Make PIP into split screen Improve the interaction between split and PIP. e.g support drag and drop pip app into split, start the pip into split, start Intent and task into split. Bug: 259161859 Test: PIP is activated and drag pip app into split. Video : https://drive.google.com/file/d/10VdfV7jfGHGRLrEdxAIWFKPhBVgwFTrI/view?usp=share_link&resourcekey=0-jZD7-EzUGc95WQIND3Jxrw Change-Id: I2d72eeb2a799e94bb53fe42ce2b439644ec8520e --- .../shell/common/TaskStackListenerImpl.java | 1 - .../wm/shell/pip/PipTaskOrganizer.java | 21 ++++- .../wm/shell/pip/phone/PipController.java | 8 +- .../splitscreen/SplitScreenController.java | 9 ++ .../shell/splitscreen/StageCoordinator.java | 94 ++++++++++++++++++- 5 files changed, 122 insertions(+), 11 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java index 9e0a48b13413b..e2106e478bb3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java @@ -216,7 +216,6 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. args.argi1 = homeTaskVisible ? 1 : 0; args.argi2 = clearedTask ? 1 : 0; args.argi3 = wasVisible ? 1 : 0; - mMainHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT); mMainHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index aad27b991db98..6412e80d671ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -1404,7 +1404,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @PipAnimationController.TransitionDirection int direction, @PipAnimationController.AnimationType int type) { final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds()); - final boolean isPipTopLeft = isPipTopLeft(); mPipBoundsState.setBounds(destinationBounds); if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { removePipImmediately(); @@ -1450,9 +1449,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, null /* callback */, false /* withStartDelay */); }); } else { - applyFinishBoundsResize(wct, direction, isPipTopLeft); + applyFinishBoundsResize(wct, direction, false); } } else { + final boolean isPipTopLeft = + direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft(); applyFinishBoundsResize(wct, direction, isPipTopLeft); } @@ -1521,6 +1522,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return topLeft.contains(mPipBoundsState.getBounds()); } + private boolean isPipToTopLeft() { + if (!mSplitScreenOptional.isPresent()) { + return false; + } + return mSplitScreenOptional.get().getActivateSplitPosition(mTaskInfo) + == SPLIT_POSITION_TOP_OR_LEFT; + } + /** * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined * and can be overridden to restore to an alternate windowing mode. @@ -1652,8 +1661,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, final Rect topLeft = new Rect(); final Rect bottomRight = new Rect(); mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight); - final boolean isPipTopLeft = isPipTopLeft(); - destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight); + destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight); return true; } @@ -1737,6 +1745,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceControlTransactionFactory = factory; } + public boolean isLaunchToSplit(TaskInfo taskInfo) { + return mSplitScreenOptional.isPresent() + && mSplitScreenOptional.get().isLaunchToSplit(taskInfo); + } + /** * Dumps internal states. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index d86468a838d95..541dc19c3829b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -570,8 +570,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb if (task.getWindowingMode() != WINDOWING_MODE_PINNED) { return; } - mTouchHandler.getMotionHelper().expandLeavePip( - clearedTask /* skipAnimation */); + if (mPipTaskOrganizer.isLaunchToSplit(task)) { + mTouchHandler.getMotionHelper().expandIntoSplit(); + } else { + mTouchHandler.getMotionHelper().expandLeavePip( + clearedTask /* skipAnimation */); + } } }); 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 36da4cf12706e..c7ad4fdcacf07 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 @@ -42,6 +42,7 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; @@ -421,6 +422,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.goToFullscreenFromSplit(); } + public boolean isLaunchToSplit(TaskInfo taskInfo) { + return mStageCoordinator.isLaunchToSplit(taskInfo); + } + + public int getActivateSplitPosition(TaskInfo taskInfo) { + return mStageCoordinator.getActivateSplitPosition(taskInfo); + } + public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { final int[] result = new int[1]; IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { 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 0f18ddaba2181..a42820d2e765d 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 @@ -74,6 +74,7 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IActivityTaskManager; import android.app.PendingIntent; +import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.ActivityNotFoundException; import android.content.Context; @@ -124,6 +125,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; +import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.common.split.SplitWindowManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; @@ -208,6 +210,36 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private DefaultMixedHandler mMixedHandler; private final Toast mSplitUnsupportedToast; + private SplitRequest mSplitRequest; + + class SplitRequest { + @SplitPosition + int mActivatePosition; + int mActivateTaskId; + int mActivateTaskId2; + Intent mStartIntent; + Intent mStartIntent2; + + SplitRequest(int taskId, Intent startIntent, int position) { + mActivateTaskId = taskId; + mStartIntent = startIntent; + mActivatePosition = position; + } + SplitRequest(Intent startIntent, int position) { + mStartIntent = startIntent; + mActivatePosition = position; + } + SplitRequest(Intent startIntent, Intent startIntent2, int position) { + mStartIntent = startIntent; + mStartIntent2 = startIntent2; + mActivatePosition = position; + } + SplitRequest(int taskId1, int taskId2, int position) { + mActivateTaskId = taskId1; + mActivateTaskId2 = taskId2; + mActivatePosition = position; + } + } private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = new SplitWindowManager.ParentContainerCallbacks() { @@ -393,6 +425,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(sideStagePosition, wct); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); targetStage.evictAllChildren(evictWct); + + // Apply surface bounds before animation start. + SurfaceControl.Transaction startT = mTransactionPool.acquire(); + if (startT != null) { + updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */); + startT.apply(); + mTransactionPool.release(startT); + } + // reparent the task to an invisible split root will make the activity invisible. Reorder + // the root task to front to make the entering transition from pip to split smooth. + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, true); + wct.reorder(targetStage.mRootTaskInfo.token, true); + wct.setForceTranslucent(targetStage.mRootTaskInfo.token, true); + // prevent the fling divider to center transition + mIsDropEntering = true; + targetStage.addTask(task, wct); if (ENABLE_SHELL_TRANSITIONS) { @@ -547,7 +596,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - if (isEnteringSplit && !openingToSide) { + if (isEnteringSplit && !openingToSide && apps != null) { mMainExecutor.execute(() -> exitSplitScreen( mSideStage.getChildCount() == 0 ? mMainStage : mSideStage, EXIT_REASON_UNKNOWN)); @@ -587,7 +636,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) { updateWindowBounds(mSplitLayout, wct); } - + mSplitRequest = new SplitRequest(intent.getIntent(), position); wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } @@ -686,6 +735,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.startTask(taskId1, options1); + mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition); startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter, instanceId); } @@ -719,6 +769,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); } else { wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); + mSplitRequest = new SplitRequest(pendingIntent1.getIntent(), + pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition); } startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio, adapter, instanceId); @@ -743,6 +795,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, addActivityOptions(options1, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); + mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition); startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter, instanceId); } @@ -815,7 +868,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Set false to avoid record new bounds with old task still on top; mShouldUpdateRecents = false; mIsSplitEntering = true; - + if (mSplitRequest == null) { + mSplitRequest = new SplitRequest(mainTaskId, + mainPendingIntent != null ? mainPendingIntent.getIntent() : null, + sidePosition); + } setSideStagePosition(sidePosition, wct); if (!mMainStage.isActive()) { mMainStage.activate(wct, false /* reparent */); @@ -906,6 +963,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, WindowContainerTransaction evictWct) { mIsSplitEntering = false; mShouldUpdateRecents = true; + mSplitRequest = null; // If any stage has no child after animation finished, it means that split will display // nothing, such status will happen if task and intent is same app but not support // multi-instance, we should exit split and expand that app as full screen. @@ -1739,7 +1797,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.init(); final WindowContainerTransaction wct = new WindowContainerTransaction(); - if (mLogger.isEnterRequestedByDrag()) { + if (mIsDropEntering) { prepareEnterSplitScreen(wct); } else { // TODO (b/238697912) : Add the validation to prevent entering non-recovered status @@ -1764,6 +1822,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; + mSplitRequest = null; updateRecentTasksSplitPair(); if (!mLogger.hasStartedSession()) { @@ -2316,6 +2375,33 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT); } + boolean isLaunchToSplit(TaskInfo taskInfo) { + return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED; + } + + int getActivateSplitPosition(TaskInfo taskInfo) { + if (mSplitRequest == null || taskInfo == null) { + return SPLIT_POSITION_UNDEFINED; + } + if (mSplitRequest.mActivateTaskId != 0 + && mSplitRequest.mActivateTaskId2 == taskInfo.taskId) { + return mSplitRequest.mActivatePosition; + } + if (mSplitRequest.mActivateTaskId == taskInfo.taskId) { + return mSplitRequest.mActivatePosition; + } + final String packageName1 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent); + final String basePackageName = SplitScreenUtils.getPackageName(taskInfo.baseIntent); + if (packageName1 != null && packageName1.equals(basePackageName)) { + return mSplitRequest.mActivatePosition; + } + final String packageName2 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent2); + if (packageName2 != null && packageName2.equals(basePackageName)) { + return mSplitRequest.mActivatePosition; + } + return SPLIT_POSITION_UNDEFINED; + } + /** Synchronize split-screen state with transition and make appropriate preparations. */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,