diff options
3 files changed, 175 insertions, 128 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index d7ca791e3863..21a13103616c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -108,6 +108,14 @@ class SplitScreenTransitions { private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { + final TransitSession pendingTransition = getPendingTransition(transition); + if (pendingTransition != null && pendingTransition.mCanceled) { + // The pending transition was canceled, so skip playing animation. + t.apply(); + onFinish(null /* wct */, null /* wctCB */); + return; + } + // Play some place-holder fade animations for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -170,9 +178,7 @@ class SplitScreenTransitions { } boolean isPendingTransition(IBinder transition) { - return isPendingEnter(transition) - || isPendingDismiss(transition) - || isPendingRecent(transition); + return getPendingTransition(transition) != null; } boolean isPendingEnter(IBinder transition) { @@ -187,22 +193,38 @@ class SplitScreenTransitions { return mPendingDismiss != null && mPendingDismiss.mTransition == transition; } + @Nullable + private TransitSession getPendingTransition(IBinder transition) { + if (isPendingEnter(transition)) { + return mPendingEnter; + } else if (isPendingRecent(transition)) { + return mPendingRecent; + } else if (isPendingDismiss(transition)) { + return mPendingDismiss; + } + + return null; + } + /** Starts a transition to enter split with a remote transition animator. */ IBinder startEnterTransition( @WindowManager.TransitionType int transitType, WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, - @Nullable TransitionCallback callback) { + @Nullable TransitionConsumedCallback consumedCallback, + @Nullable TransitionFinishedCallback finishedCallback) { final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - setEnterTransition(transition, remoteTransition, callback); + setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback); return transition; } /** Sets a transition to enter split. */ void setEnterTransition(@NonNull IBinder transition, - @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { - mPendingEnter = new TransitSession(transition, callback); + @Nullable RemoteTransition remoteTransition, + @Nullable TransitionConsumedCallback consumedCallback, + @Nullable TransitionFinishedCallback finishedCallback) { + mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) @@ -237,8 +259,9 @@ class SplitScreenTransitions { } void setRecentTransition(@NonNull IBinder transition, - @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { - mPendingRecent = new TransitSession(transition, callback); + @Nullable RemoteTransition remoteTransition, + @Nullable TransitionFinishedCallback finishCallback) { + mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) @@ -248,7 +271,7 @@ class SplitScreenTransitions { } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " - + " deduced Enter recent panel"); + + " deduced Enter recent panel"); } void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, @@ -256,14 +279,9 @@ class SplitScreenTransitions { if (mergeTarget != mAnimatingTransition) return; if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) { - mPendingRecent.mCallback = new TransitionCallback() { - @Override - public void onTransitionFinished(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) { - // Since there's an entering transition merged, recent transition no longer - // need to handle entering split screen after the transition finished. - } - }; + // Since there's an entering transition merged, recent transition no longer + // need to handle entering split screen after the transition finished. + mPendingRecent.setFinishedCallback(null); } if (mActiveRemoteHandler != null) { @@ -277,7 +295,7 @@ class SplitScreenTransitions { } boolean end() { - // If its remote, there's nothing we can do right now. + // If It's remote, there's nothing we can do right now. if (mActiveRemoteHandler != null) return false; for (int i = mAnimations.size() - 1; i >= 0; --i) { final Animator anim = mAnimations.get(i); @@ -290,20 +308,20 @@ class SplitScreenTransitions { @Nullable SurfaceControl.Transaction finishT) { if (isPendingEnter(transition)) { if (!aborted) { - // An enter transition got merged, appends the rest operations to finish entering + // An entering transition got merged, appends the rest operations to finish entering // split screen. mStageCoordinator.finishEnterSplitScreen(finishT); mPendingRemoteHandler = null; } - mPendingEnter.mCallback.onTransitionConsumed(aborted); + mPendingEnter.onConsumed(aborted); mPendingEnter = null; mPendingRemoteHandler = null; } else if (isPendingDismiss(transition)) { - mPendingDismiss.mCallback.onTransitionConsumed(aborted); + mPendingDismiss.onConsumed(aborted); mPendingDismiss = null; } else if (isPendingRecent(transition)) { - mPendingRecent.mCallback.onTransitionConsumed(aborted); + mPendingRecent.onConsumed(aborted); mPendingRecent = null; mPendingRemoteHandler = null; } @@ -312,23 +330,16 @@ class SplitScreenTransitions { void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; - TransitionCallback callback = null; + if (wct == null) wct = new WindowContainerTransaction(); if (isPendingEnter(mAnimatingTransition)) { - callback = mPendingEnter.mCallback; + mPendingEnter.onFinished(wct, mFinishTransaction); mPendingEnter = null; - } - if (isPendingDismiss(mAnimatingTransition)) { - callback = mPendingDismiss.mCallback; - mPendingDismiss = null; - } - if (isPendingRecent(mAnimatingTransition)) { - callback = mPendingRecent.mCallback; + } else if (isPendingRecent(mAnimatingTransition)) { + mPendingRecent.onFinished(wct, mFinishTransaction); mPendingRecent = null; - } - - if (callback != null) { - if (wct == null) wct = new WindowContainerTransaction(); - callback.onTransitionFinished(wct, mFinishTransaction); + } else if (isPendingDismiss(mAnimatingTransition)) { + mPendingDismiss.onFinished(wct, mFinishTransaction); + mPendingDismiss = null; } mPendingRemoteHandler = null; @@ -363,10 +374,7 @@ class SplitScreenTransitions { onFinish(null /* wct */, null /* wctCB */); }); }; - va.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { } - + va.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finisher.run(); @@ -376,9 +384,6 @@ class SplitScreenTransitions { public void onAnimationCancel(Animator animation) { finisher.run(); } - - @Override - public void onAnimationRepeat(Animator animation) { } }); mAnimations.add(va); mTransitions.getAnimExecutor().execute(va::start); @@ -432,24 +437,66 @@ class SplitScreenTransitions { || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN; } - /** Clean-up callbacks for transition. */ - interface TransitionCallback { - /** Calls when the transition got consumed. */ - default void onTransitionConsumed(boolean aborted) {} + /** Calls when the transition got consumed. */ + interface TransitionConsumedCallback { + void onConsumed(boolean aborted); + } - /** Calls when the transition finished. */ - default void onTransitionFinished(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) {} + /** Calls when the transition finished. */ + interface TransitionFinishedCallback { + void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t); } /** Session for a transition and its clean-up callback. */ static class TransitSession { final IBinder mTransition; - TransitionCallback mCallback; + TransitionConsumedCallback mConsumedCallback; + TransitionFinishedCallback mFinishedCallback; - TransitSession(IBinder transition, @Nullable TransitionCallback callback) { + /** Whether the transition was canceled. */ + boolean mCanceled; + + TransitSession(IBinder transition, + @Nullable TransitionConsumedCallback consumedCallback, + @Nullable TransitionFinishedCallback finishedCallback) { mTransition = transition; - mCallback = callback != null ? callback : new TransitionCallback() {}; + mConsumedCallback = consumedCallback; + mFinishedCallback = finishedCallback; + + } + + /** Sets transition consumed callback. */ + void setConsumedCallback(@Nullable TransitionConsumedCallback callback) { + mConsumedCallback = callback; + } + + /** Sets transition finished callback. */ + void setFinishedCallback(@Nullable TransitionFinishedCallback callback) { + mFinishedCallback = callback; + } + + /** + * Cancels the transition. This should be called before playing animation. A canceled + * transition will skip playing animation. + * + * @param finishedCb new finish callback to override. + */ + void cancel(@Nullable TransitionFinishedCallback finishedCb) { + mCanceled = true; + setFinishedCallback(finishedCb); + } + + void onConsumed(boolean aborted) { + if (mConsumedCallback != null) { + mConsumedCallback.onConsumed(aborted); + } + } + + void onFinished(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) { + if (mFinishedCallback != null) { + mFinishedCallback.onFinished(finishWct, finishT); + } } } @@ -459,7 +506,7 @@ class SplitScreenTransitions { final @SplitScreen.StageType int mDismissTop; DismissTransition(IBinder transition, int reason, int dismissTop) { - super(transition, null /* callback */); + super(transition, null /* consumedCallback */, null /* finishedCallback */); this.mReason = reason; this.mDismissTop = dismissTop; } 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 e2ac01f7b003..943419bb8ea2 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 @@ -226,33 +226,36 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; - private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback = - new SplitScreenTransitions.TransitionCallback() { - @Override - public void onTransitionFinished(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) { - // Check if the recent transition is finished by returning to the current split, so we - // can restore the divider bar. - for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { - final WindowContainerTransaction.HierarchyOp op = - finishWct.getHierarchyOps().get(i); - final IBinder container = op.getContainer(); - if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() - && (mMainStage.containsContainer(container) - || mSideStage.containsContainer(container))) { - updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); - setDividerVisibility(true, finishT); - return; - } - } + private final SplitScreenTransitions.TransitionFinishedCallback + mRecentTransitionFinishedCallback = + new SplitScreenTransitions.TransitionFinishedCallback() { + @Override + public void onFinished(WindowContainerTransaction finishWct, + SurfaceControl.Transaction finishT) { + // Check if the recent transition is finished by returning to the current + // split, so we + // can restore the divider bar. + for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { + final WindowContainerTransaction.HierarchyOp op = + finishWct.getHierarchyOps().get(i); + final IBinder container = op.getContainer(); + if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() + && (mMainStage.containsContainer(container) + || mSideStage.containsContainer(container))) { + updateSurfaceBounds(mSplitLayout, finishT, + false /* applyResizingOffset */); + setDividerVisibility(true, finishT); + return; + } + } - // Dismiss the split screen if it's not returning to split. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); - setSplitsVisible(false); - setDividerVisibility(false, finishT); - logExit(EXIT_REASON_UNKNOWN); - } - }; + // Dismiss the split screen if it's not returning to split. + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); + setSplitsVisible(false); + setDividerVisibility(false, finishT); + logExit(EXIT_REASON_UNKNOWN); + } + }; protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, @@ -389,15 +392,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (ENABLE_SHELL_TRANSITIONS) { prepareEnterSplitScreen(wct); mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, - null, this, new SplitScreenTransitions.TransitionCallback() { - @Override - public void onTransitionFinished(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) { - if (!evictWct.isEmpty()) { - finishWct.merge(evictWct, true); - } + null, this, null /* consumedCallback */, (finishWct, finishT) -> { + if (!evictWct.isEmpty()) { + finishWct.merge(evictWct, true); } - }); + } /* finishedCallback */); } else { if (!evictWct.isEmpty()) { wct.merge(evictWct, true /* transfer */); @@ -434,28 +433,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); wct.sendPendingIntent(intent, fillInIntent, options); + + // If split screen is not activated, we're expecting to open a pair of apps to split. + final int transitType = mMainStage.isActive() + ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; prepareEnterSplitScreen(wct, null /* taskInfo */, position); - mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this, - new SplitScreenTransitions.TransitionCallback() { - @Override - public void onTransitionConsumed(boolean aborted) { - // Switch the split position if launching as MULTIPLE_TASK failed. - if (aborted - && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { - setSideStagePositionAnimated( - SplitLayout.reversePosition(mSideStagePosition)); - } + mSplitTransitions.startEnterTransition(transitType, wct, null, this, + aborted -> { + // Switch the split position if launching as MULTIPLE_TASK failed. + if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { + setSideStagePositionAnimated( + SplitLayout.reversePosition(mSideStagePosition)); } - - @Override - public void onTransitionFinished(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) { - if (!evictWct.isEmpty()) { - finishWct.merge(evictWct, true); - } + } /* consumedCallback */, + (finishWct, finishT) -> { + if (!evictWct.isEmpty()) { + finishWct.merge(evictWct, true); } - }); + } /* finishedCallback */); } /** Launches an activity into split by legacy transition. */ @@ -564,9 +560,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** * Starts with the second task to a split pair in one transition. * - * @param wct transaction to start the first task + * @param wct transaction to start the first task * @param instanceId if {@code null}, will not log. Otherwise it will be used in - * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} + * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} */ private void startWithTask(WindowContainerTransaction wct, int mainTaskId, @Nullable Bundle mainOptions, float splitRatio, @@ -592,7 +588,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startTask(mainTaskId, mainOptions); mSplitTransitions.startEnterTransition( - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null); + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null); setEnterInstanceId(instanceId); } @@ -639,7 +635,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } /** - * @param wct transaction to start the first task + * @param wct transaction to start the first task * @param instanceId if {@code null}, will not log. Otherwise it will be used in * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)} */ @@ -1082,15 +1078,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, switch (exitReason) { // One of the apps doesn't support MW case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW: - // User has explicitly dragged the divider to dismiss split + // User has explicitly dragged the divider to dismiss split case EXIT_REASON_DRAG_DIVIDER: - // Either of the split apps have finished + // Either of the split apps have finished case EXIT_REASON_APP_FINISHED: - // One of the children enters PiP + // One of the children enters PiP case EXIT_REASON_CHILD_TASK_ENTER_PIP: - // One of the apps occludes lock screen. + // One of the apps occludes lock screen. case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP: - // User has unlocked the device after folded + // User has unlocked the device after folded case EXIT_REASON_DEVICE_FOLDED: return true; default: @@ -1839,7 +1835,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(), - mRecentTransitionCallback); + mRecentTransitionFinishedCallback); } else if (mSplitTransitions.mPendingRecent == null) { // If split-task is not controlled by recents animation // and occluded by the other fullscreen task, dismiss both. @@ -1853,8 +1849,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); - mSplitTransitions.setEnterTransition( - transition, request.getRemoteTransition(), null /* callback */); + mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), + null /* consumedCallback */, null /* finishedCallback */); } } return out; @@ -1873,7 +1869,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } final @WindowManager.TransitionType int type = request.getType(); if (isSplitActive() && !isOpeningType(type) - && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) { + && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became " + "empty during a mixed transition (one not handled by split)," + " so make sure split-screen state is cleaned-up. " @@ -2031,17 +2027,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before - // applying anything here. Ideally consolidate with transition-merging. if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { if (mainChild == null && sideChild == null) { - throw new IllegalStateException("Launched a task in split, but didn't receive any" - + " task in transition."); + Log.w(TAG, "Launched a task in split, but didn't receive any task in transition."); + mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */); + return true; } } else { if (mainChild == null || sideChild == null) { - throw new IllegalStateException("Launched 2 tasks in split, but didn't receive" + Log.w(TAG, "Launched 2 tasks in split, but didn't receive" + " 2 tasks in transition. Possibly one of them failed to launch"); + final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : + (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); + mSplitTransitions.mPendingEnter.cancel( + (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); + return true; } } @@ -2305,7 +2305,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(stageType, wct); - mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType, + mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); mSplitUnsupportedToast.show(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index ea0033ba4bbb..652f9b38c88f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -181,7 +181,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), - new RemoteTransition(testRemote), mStageCoordinator, null); + new RemoteTransition(testRemote), mStageCoordinator, null, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, @@ -421,7 +421,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo enterInfo = createEnterPairInfo(); IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), - new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null); + new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, |