diff options
4 files changed, 749 insertions, 544 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index bf783e6af36f..8c2203ef7a49 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -21,17 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; import static android.view.WindowManager.TRANSIT_PIP; -import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; -import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; -import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; @@ -56,7 +48,6 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; -import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -84,7 +75,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private UnfoldTransitionHandler mUnfoldHandler; private ActivityEmbeddingController mActivityEmbeddingController; - private static class MixedTransition { + abstract static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; /** Both the display and split-state (enter/exit) is changing */ @@ -124,15 +115,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, int mAnimType = ANIM_TYPE_DEFAULT; final IBinder mTransition; - private final Transitions mPlayer; - private final DefaultMixedHandler mMixedHandler; - private final PipTransitionController mPipHandler; - private final RecentsTransitionHandler mRecentsHandler; - private final StageCoordinator mSplitHandler; - private final KeyguardTransitionHandler mKeyguardHandler; - private final DesktopTasksController mDesktopTasksController; - private final UnfoldTransitionHandler mUnfoldHandler; - private final ActivityEmbeddingController mActivityEmbeddingController; + protected final Transitions mPlayer; + protected final DefaultMixedHandler mMixedHandler; + protected final PipTransitionController mPipHandler; + protected final StageCoordinator mSplitHandler; + protected final KeyguardTransitionHandler mKeyguardHandler; Transitions.TransitionHandler mLeftoversHandler = null; TransitionInfo mInfo = null; @@ -156,409 +143,33 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, MixedTransition(int type, IBinder transition, Transitions player, DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, - RecentsTransitionHandler recentsHandler, StageCoordinator splitHandler, - KeyguardTransitionHandler keyguardHandler, - DesktopTasksController desktopTasksController, - UnfoldTransitionHandler unfoldHandler, - ActivityEmbeddingController activityEmbeddingController) { + StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) { mType = type; mTransition = transition; mPlayer = player; mMixedHandler = mixedHandler; mPipHandler = pipHandler; - mRecentsHandler = recentsHandler; mSplitHandler = splitHandler; mKeyguardHandler = keyguardHandler; - mDesktopTasksController = desktopTasksController; - mUnfoldHandler = unfoldHandler; - mActivityEmbeddingController = activityEmbeddingController; - - switch (type) { - case TYPE_RECENTS_DURING_DESKTOP: - case TYPE_RECENTS_DURING_KEYGUARD: - case TYPE_RECENTS_DURING_SPLIT: - mLeftoversHandler = mRecentsHandler; - break; - case TYPE_UNFOLD: - mLeftoversHandler = mUnfoldHandler; - break; - case TYPE_DISPLAY_AND_SPLIT_CHANGE: - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - case TYPE_ENTER_PIP_FROM_SPLIT: - case TYPE_KEYGUARD: - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - default: - break; - } } - boolean startAnimation( + abstract boolean startAnimation( @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - switch (mType) { - case TYPE_ENTER_PIP_FROM_SPLIT: - return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, - finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - return animateEnterPipFromActivityEmbedding( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_DISPLAY_AND_SPLIT_CHANGE: - return false; - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - final boolean handledToPip = animateOpenIntentWithRemoteAndPip( - info, startTransaction, finishTransaction, finishCallback); - // Consume the transition on remote handler if the leftover handler already - // handle this transition. And if it cannot, the transition will be handled by - // remote handler, so don't consume here. - // Need to check leftOverHandler as it may change in - // #animateOpenIntentWithRemoteAndPip - if (handledToPip && mHasRequestToRemote - && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { - mPlayer.getRemoteTransitionHandler().onTransitionConsumed( - transition, false, null); - } - return handledToPip; - case TYPE_RECENTS_DURING_SPLIT: - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - // Pip auto-entering info might be appended to recent transition like - // pressing home-key in 3-button navigation. This offers split handler the - // opportunity to handle split to pip animation. - if (mPipHandler.isEnteringPip(change, info.getType()) - && mSplitHandler.getSplitItemPosition(change.getLastParent()) - != SPLIT_POSITION_UNDEFINED) { - return animateEnterPipFromSplit( - this, info, startTransaction, finishTransaction, finishCallback, - mPlayer, mMixedHandler, mPipHandler, mSplitHandler); - } - } - - return animateRecentsDuringSplit( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_KEYGUARD: - return animateKeyguard(this, info, startTransaction, finishTransaction, - finishCallback, mKeyguardHandler, mPipHandler); - case TYPE_RECENTS_DURING_KEYGUARD: - return animateRecentsDuringKeyguard( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_RECENTS_DURING_DESKTOP: - return animateRecentsDuringDesktop( - info, startTransaction, finishTransaction, finishCallback); - case TYPE_UNFOLD: - return animateUnfold( - info, startTransaction, finishTransaction, finishCallback); - default: - throw new IllegalStateException( - "Starting mixed animation without a known mixed type? " + mType); - } - } - - private boolean animateEnterPipFromActivityEmbedding( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " - + "entering PIP from an Activity Embedding window"); - // Split into two transitions (wct) - TransitionInfo.Change pipChange = null; - final TransitionInfo everythingElse = - subCopy(info, TRANSIT_TO_BACK, true /* changes */); - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (mPipHandler.isEnteringPip(change, info.getType())) { - if (pipChange != null) { - throw new IllegalStateException("More than 1 pip-entering changes in one" - + " transition? " + info); - } - pipChange = change; - // going backwards, so remove-by-index is fine. - everythingElse.getChanges().remove(i); - } - } - - final Transitions.TransitionFinishCallback finishCB = (wct) -> { - --mInFlightSubAnimations; - joinFinishArgs(wct); - if (mInFlightSubAnimations > 0) return; - finishCallback.onTransitionFinished(mFinishWCT); - }; - - if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { - // Fallback to dispatching to other handlers. - return false; - } - - // PIP window should always be on the highest Z order. - if (pipChange != null) { - mInFlightSubAnimations = 2; - mPipHandler.startEnterAnimation( - pipChange, - startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), - finishTransaction, - finishCB); - } else { - mInFlightSubAnimations = 1; - } - - mActivityEmbeddingController.startAnimation(mTransition, everythingElse, - startTransaction, finishTransaction, finishCB); - return true; - } + @NonNull Transitions.TransitionFinishCallback finishCallback); - private boolean animateOpenIntentWithRemoteAndPip( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - TransitionInfo.Change pipChange = null; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (mPipHandler.isEnteringPip(change, info.getType())) { - if (pipChange != null) { - throw new IllegalStateException("More than 1 pip-entering changes in one" - + " transition? " + info); - } - pipChange = change; - info.getChanges().remove(i); - } - } - Transitions.TransitionFinishCallback finishCB = (wct) -> { - --mInFlightSubAnimations; - joinFinishArgs(wct); - if (mInFlightSubAnimations > 0) return; - finishCallback.onTransitionFinished(mFinishWCT); - }; - if (pipChange == null) { - if (mLeftoversHandler != null) { - mInFlightSubAnimations = 1; - if (mLeftoversHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB)) { - return true; - } - } - return false; - } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate" - + " animation because remote-animation likely doesn't support it"); - // Split the transition into 2 parts: the pip part and the rest. - mInFlightSubAnimations = 2; - // make a new startTransaction because pip's startEnterAnimation "consumes" it so - // we need a separate one to send over to launcher. - SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); - - mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB); - - // Dispatch the rest of the transition normally. - if (mLeftoversHandler != null - && mLeftoversHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB)) { - return true; - } - mLeftoversHandler = mPlayer.dispatchTransition(mTransition, info, - startTransaction, finishTransaction, finishCB, mMixedHandler); - return true; - } - - private boolean animateRecentsDuringSplit( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - // Split-screen is only interested in the recents transition finishing (and merging), so - // just wrap finish and start recents animation directly. - Transitions.TransitionFinishCallback finishCB = (wct) -> { - mInFlightSubAnimations = 0; - // If pair-to-pair switching, the post-recents clean-up isn't needed. - wct = wct != null ? wct : new WindowContainerTransaction(); - if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { - mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); - } else { - // notify pair-to-pair recents animation finish - mSplitHandler.onRecentsPairToPairAnimationFinish(wct); - } - mSplitHandler.onTransitionAnimationComplete(); - finishCallback.onTransitionFinished(wct); - }; - mInFlightSubAnimations = 1; - mSplitHandler.onRecentsInSplitAnimationStart(info); - final boolean handled = mLeftoversHandler.startAnimation(mTransition, info, - startTransaction, finishTransaction, finishCB); - if (!handled) { - mSplitHandler.onRecentsInSplitAnimationCanceled(); - } - return handled; - } - - private boolean animateRecentsDuringKeyguard( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (mInfo == null) { - mInfo = info; - mFinishT = finishTransaction; - mFinishCB = finishCallback; - } - return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction); - } - - private boolean animateRecentsDuringDesktop( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - Transitions.TransitionFinishCallback finishCB = wct -> { - mInFlightSubAnimations--; - if (mInFlightSubAnimations == 0) { - finishCallback.onTransitionFinished(wct); - } - }; - - mInFlightSubAnimations++; - boolean consumed = mRecentsHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB); - if (!consumed) { - mInFlightSubAnimations--; - return false; - } - if (mDesktopTasksController != null) { - mDesktopTasksController.syncSurfaceState(info, finishTransaction); - return true; - } - - return false; - } - - private boolean animateUnfold( - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - final Transitions.TransitionFinishCallback finishCB = (wct) -> { - mInFlightSubAnimations--; - if (mInFlightSubAnimations > 0) return; - finishCallback.onTransitionFinished(wct); - }; - mInFlightSubAnimations = 1; - // Sync pip state. - if (mPipHandler != null) { - mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); - } - if (mSplitHandler != null && mSplitHandler.isSplitActive()) { - mSplitHandler.updateSurfaces(startTransaction); - } - return mUnfoldHandler.startAnimation( - mTransition, info, startTransaction, finishTransaction, finishCB); - } - - void mergeAnimation( + abstract void mergeAnimation( @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - switch (mType) { - case TYPE_DISPLAY_AND_SPLIT_CHANGE: - // queue since no actual animation. - break; - case TYPE_ENTER_PIP_FROM_SPLIT: - if (mAnimType == ANIM_TYPE_GOING_HOME) { - boolean ended = mSplitHandler.end(); - // If split couldn't end (because it is remote), then don't end everything - // else since we have to play out the animation anyways. - if (!ended) return; - mPipHandler.end(); - if (mLeftoversHandler != null) { - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - } - } else { - mPipHandler.end(); - } - break; - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - mPipHandler.end(); - mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, - finishCallback); - break; - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - mPipHandler.end(); - if (mLeftoversHandler != null) { - mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, - finishCallback); - } - break; - case TYPE_RECENTS_DURING_SPLIT: - if (mSplitHandler.isPendingEnter(transition)) { - // Recents -> enter-split means that we are switching from one pair to - // another pair. - mAnimType = ANIM_TYPE_PAIR_TO_PAIR; - } - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_KEYGUARD: - mKeyguardHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_RECENTS_DURING_KEYGUARD: - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { - DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT); - if (animateKeyguard(this, info, t, mFinishT, mFinishCB, mKeyguardHandler, - mPipHandler)) { - finishCallback.onTransitionFinished(null); - } - } - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_RECENTS_DURING_DESKTOP: - mLeftoversHandler.mergeAnimation( - transition, info, t, mergeTarget, finishCallback); - break; - case TYPE_UNFOLD: - mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); - break; - default: - throw new IllegalStateException( - "Playing a mixed transition with unknown type? " + mType); - } - } + @NonNull Transitions.TransitionFinishCallback finishCallback); - void onTransitionConsumed( + abstract void onTransitionConsumed( @NonNull IBinder transition, boolean aborted, - @Nullable SurfaceControl.Transaction finishT) { - switch (mType) { - case TYPE_ENTER_PIP_FROM_SPLIT: - mPipHandler.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: - mPipHandler.onTransitionConsumed(transition, aborted, finishT); - mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_RECENTS_DURING_SPLIT: - case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: - case TYPE_RECENTS_DURING_DESKTOP: - mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_KEYGUARD: - mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); - break; - case TYPE_UNFOLD: - mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); - break; - default: - break; - } - - if (mHasRequestToRemote) { - mPlayer.getRemoteTransitionHandler().onTransitionConsumed( - transition, aborted, finishT); - } - } + @Nullable SurfaceControl.Transaction finishT); - boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info, + protected boolean startSubAnimation( + Transitions.TransitionHandler handler, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { if (mInfo != null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, @@ -573,7 +184,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return true; } - void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) { + private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) { mInFlightSubAnimations--; if (mInfo != null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, @@ -644,7 +255,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, throw new IllegalStateException("Unexpected remote transition in" + "pip-enter-from-split request"); } - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createDefaultMixedTransition( MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition)); WindowContainerTransaction out = new WindowContainerTransaction(); @@ -656,7 +267,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mActivityEmbeddingController != null)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request from an Activity Embedding split"); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createDefaultMixedTransition( MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); // Postpone transition splitting to later. WindowContainerTransaction out = new WindowContainerTransaction(); @@ -675,7 +286,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (handler == null) { return null; } - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createDefaultMixedTransition( MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); @@ -701,7 +312,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mPlayer.getRemoteTransitionHandler(), new WindowContainerTransaction()); } - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); @@ -710,7 +321,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, final WindowContainerTransaction wct = mUnfoldHandler.handleRequest(transition, request); if (wct != null) { - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createDefaultMixedTransition( MixedTransition.TYPE_UNFOLD, transition)); } return wct; @@ -718,6 +329,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return null; } + private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) { + return new DefaultMixedTransition( + type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler, + mUnfoldHandler, mActivityEmbeddingController); + } + @Override public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) { if (mRecentsHandler != null) { @@ -737,31 +354,30 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private void setRecentsTransitionDuringSplit(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "Split-Screen is foreground, so treat it as Mixed."); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition)); } private void setRecentsTransitionDuringKeyguard(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "keyguard is visible, so treat it as Mixed."); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition)); } private void setRecentsTransitionDuringDesktop(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "desktop mode is active, so treat it as Mixed."); - mActiveTransitions.add(createMixedTransition( + mActiveTransitions.add(createRecentsMixedTransition( MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition)); } - private MixedTransition createMixedTransition(int type, IBinder transition) { - return new MixedTransition(type, transition, mPlayer, this, mPipHandler, mRecentsHandler, - mSplitHandler, mKeyguardHandler, mDesktopTasksController, mUnfoldHandler, - mActivityEmbeddingController); + private MixedTransition createRecentsMixedTransition(int type, IBinder transition) { + return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler, + mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController); } - private static TransitionInfo subCopy(@NonNull TransitionInfo info, + static TransitionInfo subCopy(@NonNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges) { final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); out.setTrack(info.getTrack()); @@ -778,15 +394,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return out; } - private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) { - return change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; - } - - private static boolean isWallpaper(@NonNull TransitionInfo.Change change) { - return (change.getFlags() & FLAG_IS_WALLPAPER) != 0; - } - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -805,7 +412,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (KeyguardTransitionHandler.handles(info)) { if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) { final MixedTransition keyguardMixed = - createMixedTransition(MixedTransition.TYPE_KEYGUARD, transition); + createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition); mActiveTransitions.add(keyguardMixed); Transitions.TransitionFinishCallback callback = wct -> { mActiveTransitions.remove(keyguardMixed); @@ -845,117 +452,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return handled; } - private static boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed, - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler, - @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " - + "entering PIP while Split-Screen is foreground."); - TransitionInfo.Change pipChange = null; - TransitionInfo.Change wallpaper = null; - final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); - boolean homeIsOpening = false; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (pipHandler.isEnteringPip(change, info.getType())) { - if (pipChange != null) { - throw new IllegalStateException("More than 1 pip-entering changes in one" - + " transition? " + info); - } - pipChange = change; - // going backwards, so remove-by-index is fine. - everythingElse.getChanges().remove(i); - } else if (isHomeOpening(change)) { - homeIsOpening = true; - } else if (isWallpaper(change)) { - wallpaper = change; - } - } - if (pipChange == null) { - // um, something probably went wrong. - return false; - } - final boolean isGoingHome = homeIsOpening; - Transitions.TransitionFinishCallback finishCB = (wct) -> { - --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct); - if (mixed.mInFlightSubAnimations > 0) return; - if (isGoingHome) { - splitHandler.onTransitionAnimationComplete(); - } - finishCallback.onTransitionFinished(mixed.mFinishWCT); - }; - if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent()) - != SPLIT_POSITION_UNDEFINED) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed " - + "since entering-PiP caused us to leave split and return home."); - // We need to split the transition into 2 parts: the pip part (animated by pip) - // and the dismiss-part (animated by launcher). - mixed.mInFlightSubAnimations = 2; - // immediately make the wallpaper visible (so that we don't see it pop-in during - // the time it takes to start recents animation (which is remote). - if (wallpaper != null) { - startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f); - } - // make a new startTransaction because pip's startEnterAnimation "consumes" it so - // we need a separate one to send over to launcher. - SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); - @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; - if (splitHandler.isSplitScreenVisible()) { - // The non-going home case, we could be pip-ing one of the split stages and keep - // showing the other - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - TransitionInfo.Change change = info.getChanges().get(i); - if (change == pipChange) { - // Ignore the change/task that's going into Pip - continue; - } - @SplitScreen.StageType int splitItemStage = - splitHandler.getSplitItemStage(change.getLastParent()); - if (splitItemStage != STAGE_TYPE_UNDEFINED) { - topStageToKeep = splitItemStage; - break; - } - } - } - // Let split update internal state for dismiss. - splitHandler.prepareDismissAnimation(topStageToKeep, - EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, - finishTransaction); - - // We are trying to accommodate launcher's close animation which can't handle the - // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove - // from transition info. - for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) { - if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) { - everythingElse.getChanges().remove(i); - break; - } - } - - pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); - pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, - finishCB); - // Dispatch the rest of the transition normally. This will most-likely be taken by - // recents or default handler. - mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse, - otherStartT, finishTransaction, finishCB, mixedHandler); - } else { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " - + "forward animation to Pip-Handler."); - // This happens if the pip-ing activity is in a multi-activity task (and thus a - // new pip task is spawned). In this case, we don't actually exit split so we can - // just let pip transition handle the animation verbatim. - mixed.mInFlightSubAnimations = 1; - pipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, - finishCB); - } - return true; - } - private void unlinkMissingParents(TransitionInfo from) { for (int i = 0; i < from.getChanges().size(); ++i) { final TransitionInfo.Change chg = from.getChanges().get(i); @@ -987,15 +483,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCallback) { - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createDefaultMixedTransition( MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition); mActiveTransitions.add(mixed); Transitions.TransitionFinishCallback callback = wct -> { mActiveTransitions.remove(mixed); finishCallback.onTransitionFinished(wct); }; - return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback, mPlayer, this, - mPipHandler, mSplitHandler); + return mixed.startAnimation(transition, info, startT, finishT, callback); } /** @@ -1018,7 +513,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } if (displayPart.getChanges().isEmpty()) return false; unlinkMissingParents(everythingElse); - final MixedTransition mixed = createMixedTransition( + final MixedTransition mixed = createDefaultMixedTransition( MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition); mActiveTransitions.add(mixed); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change " @@ -1135,7 +630,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, * {@link TransitionInfo} so that it can take over some parts of the animation without * reparenting to new transition roots. */ - private static void handoverTransitionLeashes( + static void handoverTransitionLeashes( @NonNull TransitionInfo from, @NonNull TransitionInfo to, @NonNull SurfaceControl.Transaction startT, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java new file mode 100644 index 000000000000..9ce46d69815b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.view.WindowManager.TRANSIT_TO_BACK; + +import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController; +import com.android.wm.shell.keyguard.KeyguardTransitionHandler; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.splitscreen.StageCoordinator; +import com.android.wm.shell.unfold.UnfoldTransitionHandler; + +class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { + private final UnfoldTransitionHandler mUnfoldHandler; + private final ActivityEmbeddingController mActivityEmbeddingController; + + DefaultMixedTransition(int type, IBinder transition, Transitions player, + DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, + StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler, + UnfoldTransitionHandler unfoldHandler, + ActivityEmbeddingController activityEmbeddingController) { + super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler); + mUnfoldHandler = unfoldHandler; + mActivityEmbeddingController = activityEmbeddingController; + + switch (type) { + case TYPE_UNFOLD: + mLeftoversHandler = mUnfoldHandler; + break; + case TYPE_DISPLAY_AND_SPLIT_CHANGE: + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + case TYPE_ENTER_PIP_FROM_SPLIT: + case TYPE_KEYGUARD: + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + default: + break; + } + } + + @Override + boolean startAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + return switch (mType) { + case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false; + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING -> + animateEnterPipFromActivityEmbedding( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_ENTER_PIP_FROM_SPLIT -> + animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + case TYPE_KEYGUARD -> + animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback, + mKeyguardHandler, mPipHandler); + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE -> + animateOpenIntentWithRemoteAndPip(transition, info, startTransaction, + finishTransaction, finishCallback); + case TYPE_UNFOLD -> + animateUnfold(info, startTransaction, finishTransaction, finishCallback); + default -> throw new IllegalStateException( + "Starting default mixed animation with unknown or illegal type: " + mType); + }; + } + + private boolean animateEnterPipFromActivityEmbedding( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP from an Activity Embedding window"); + // Split into two transitions (wct) + TransitionInfo.Change pipChange = null; + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } + } + + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mInFlightSubAnimations; + joinFinishArgs(wct); + if (mInFlightSubAnimations > 0) return; + finishCallback.onTransitionFinished(mFinishWCT); + }; + + if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { + // Fallback to dispatching to other handlers. + return false; + } + + // PIP window should always be on the highest Z order. + if (pipChange != null) { + mInFlightSubAnimations = 2; + mPipHandler.startEnterAnimation( + pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), + finishTransaction, + finishCB); + } else { + mInFlightSubAnimations = 1; + } + + mActivityEmbeddingController.startAnimation( + mTransition, everythingElse, startTransaction, finishTransaction, finishCB); + return true; + } + + private boolean animateOpenIntentWithRemoteAndPip( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip( + info, startTransaction, finishTransaction, finishCallback); + // Consume the transition on remote handler if the leftover handler already handle this + // transition. And if it cannot, the transition will be handled by remote handler, so don't + // consume here. + // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip + if (handledToPip && mHasRequestToRemote + && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null); + } + return handledToPip; + } + + private boolean tryAnimateOpenIntentWithRemoteAndPip( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + TransitionInfo.Change pipChange = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + info.getChanges().remove(i); + } + } + Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mInFlightSubAnimations; + joinFinishArgs(wct); + if (mInFlightSubAnimations > 0) return; + finishCallback.onTransitionFinished(mFinishWCT); + }; + if (pipChange == null) { + if (mLeftoversHandler != null) { + mInFlightSubAnimations = 1; + if (mLeftoversHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB)) { + return true; + } + } + return false; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate" + + " animation because remote-animation likely doesn't support it"); + // Split the transition into 2 parts: the pip part and the rest. + mInFlightSubAnimations = 2; + // make a new startTransaction because pip's startEnterAnimation "consumes" it so + // we need a separate one to send over to launcher. + SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + + mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB); + + // Dispatch the rest of the transition normally. + if (mLeftoversHandler != null + && mLeftoversHandler.startAnimation(mTransition, info, + startTransaction, finishTransaction, finishCB)) { + return true; + } + mLeftoversHandler = mPlayer.dispatchTransition( + mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler); + return true; + } + + private boolean animateUnfold( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final Transitions.TransitionFinishCallback finishCB = (wct) -> { + mInFlightSubAnimations--; + if (mInFlightSubAnimations > 0) return; + finishCallback.onTransitionFinished(wct); + }; + mInFlightSubAnimations = 1; + // Sync pip state. + if (mPipHandler != null) { + mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); + } + if (mSplitHandler != null && mSplitHandler.isSplitActive()) { + mSplitHandler.updateSurfaces(startTransaction); + } + return mUnfoldHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB); + } + + @Override + void mergeAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + switch (mType) { + case TYPE_DISPLAY_AND_SPLIT_CHANGE: + // queue since no actual animation. + return; + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + mPipHandler.end(); + mActivityEmbeddingController.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + return; + case TYPE_ENTER_PIP_FROM_SPLIT: + if (mAnimType == ANIM_TYPE_GOING_HOME) { + boolean ended = mSplitHandler.end(); + // If split couldn't end (because it is remote), then don't end everything else + // since we have to play out the animation anyways. + if (!ended) return; + mPipHandler.end(); + if (mLeftoversHandler != null) { + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + } + } else { + mPipHandler.end(); + } + return; + case TYPE_KEYGUARD: + mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + mPipHandler.end(); + if (mLeftoversHandler != null) { + mLeftoversHandler.mergeAnimation( + transition, info, t, mergeTarget, finishCallback); + } + return; + case TYPE_UNFOLD: + mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + default: + throw new IllegalStateException("Playing a default mixed transition with unknown or" + + " illegal type: " + mType); + } + } + + @Override + void onTransitionConsumed( + @NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishT) { + switch (mType) { + case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING: + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_ENTER_PIP_FROM_SPLIT: + mPipHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_KEYGUARD: + mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE: + mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); + break; + case TYPE_UNFOLD: + mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); + break; + default: + break; + } + + if (mHasRequestToRemote) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java new file mode 100644 index 000000000000..0974cd13f249 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; + +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; +import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; + +import android.annotation.NonNull; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.keyguard.KeyguardTransitionHandler; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.splitscreen.StageCoordinator; + +public class MixedTransitionHelper { + static boolean animateEnterPipFromSplit( + @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler, + @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP while Split-Screen is foreground."); + TransitionInfo.Change pipChange = null; + TransitionInfo.Change wallpaper = null; + final TransitionInfo everythingElse = + subCopy(info, TRANSIT_TO_BACK, true /* changes */); + boolean homeIsOpening = false; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (pipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } else if (isHomeOpening(change)) { + homeIsOpening = true; + } else if (isWallpaper(change)) { + wallpaper = change; + } + } + if (pipChange == null) { + // um, something probably went wrong. + return false; + } + final boolean isGoingHome = homeIsOpening; + Transitions.TransitionFinishCallback finishCB = (wct) -> { + --mixed.mInFlightSubAnimations; + mixed.joinFinishArgs(wct); + if (mixed.mInFlightSubAnimations > 0) return; + if (isGoingHome) { + splitHandler.onTransitionAnimationComplete(); + } + finishCallback.onTransitionFinished(mixed.mFinishWCT); + }; + if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent()) + != SPLIT_POSITION_UNDEFINED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed " + + "since entering-PiP caused us to leave split and return home."); + // We need to split the transition into 2 parts: the pip part (animated by pip) + // and the dismiss-part (animated by launcher). + mixed.mInFlightSubAnimations = 2; + // immediately make the wallpaper visible (so that we don't see it pop-in during + // the time it takes to start recents animation (which is remote). + if (wallpaper != null) { + startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f); + } + // make a new startTransaction because pip's startEnterAnimation "consumes" it so + // we need a separate one to send over to launcher. + SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (splitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + splitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } + // Let split update internal state for dismiss. + splitHandler.prepareDismissAnimation(topStageToKeep, + EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, + finishTransaction); + + // We are trying to accommodate launcher's close animation which can't handle the + // divider-bar, so if split-handler is closing the divider-bar, just hide it and + // remove from transition info. + for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) { + if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) + != 0) { + everythingElse.getChanges().remove(i); + break; + } + } + + pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); + pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, + finishCB); + // Dispatch the rest of the transition normally. This will most-likely be taken by + // recents or default handler. + mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse, + otherStartT, finishTransaction, finishCB, mixedHandler); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " + + "forward animation to Pip-Handler."); + // This happens if the pip-ing activity is in a multi-activity task (and thus a + // new pip task is spawned). In this case, we don't actually exit split so we can + // just let pip transition handle the animation verbatim. + mixed.mInFlightSubAnimations = 1; + pipHandler.startAnimation( + mixed.mTransition, info, startTransaction, finishTransaction, finishCB); + } + return true; + } + + private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) { + return change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; + } + + private static boolean isWallpaper(@NonNull TransitionInfo.Change change) { + return (change.getFlags() & FLAG_IS_WALLPAPER) != 0; + } + + static boolean animateKeyguard( + @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull KeyguardTransitionHandler keyguardHandler, + PipTransitionController pipHandler) { + if (mixed.mFinishT == null) { + mixed.mFinishT = finishTransaction; + mixed.mFinishCB = finishCallback; + } + // Sync pip state. + if (pipHandler != null) { + pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); + } + return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java new file mode 100644 index 000000000000..643e0266d7df --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; + +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit; +import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.keyguard.KeyguardTransitionHandler; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.splitscreen.StageCoordinator; + +class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { + private final RecentsTransitionHandler mRecentsHandler; + private final DesktopTasksController mDesktopTasksController; + + RecentsMixedTransition(int type, IBinder transition, Transitions player, + DefaultMixedHandler mixedHandler, PipTransitionController pipHandler, + StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler, + RecentsTransitionHandler recentsHandler, + DesktopTasksController desktopTasksController) { + super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler); + mRecentsHandler = recentsHandler; + mDesktopTasksController = desktopTasksController; + mLeftoversHandler = mRecentsHandler; + } + + @Override + boolean startAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + return switch (mType) { + case TYPE_RECENTS_DURING_DESKTOP -> + animateRecentsDuringDesktop( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_RECENTS_DURING_KEYGUARD -> + animateRecentsDuringKeyguard( + info, startTransaction, finishTransaction, finishCallback); + case TYPE_RECENTS_DURING_SPLIT -> + animateRecentsDuringSplit( + info, startTransaction, finishTransaction, finishCallback); + default -> throw new IllegalStateException( + "Starting Recents mixed animation with unknown or illegal type: " + mType); + }; + } + + private boolean animateRecentsDuringDesktop( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mInfo == null) { + mInfo = info; + mFinishT = finishTransaction; + mFinishCB = finishCallback; + } + Transitions.TransitionFinishCallback finishCB = wct -> { + mInFlightSubAnimations--; + if (mInFlightSubAnimations == 0) { + finishCallback.onTransitionFinished(wct); + } + }; + + mInFlightSubAnimations++; + boolean consumed = mRecentsHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB); + if (!consumed) { + mInFlightSubAnimations--; + return false; + } + if (mDesktopTasksController != null) { + mDesktopTasksController.syncSurfaceState(info, finishTransaction); + return true; + } + + return false; + } + + private boolean animateRecentsDuringKeyguard( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mInfo == null) { + mInfo = info; + mFinishT = finishTransaction; + mFinishCB = finishCallback; + } + return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction); + } + + private boolean animateRecentsDuringSplit( + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + // Pip auto-entering info might be appended to recent transition like pressing + // home-key in 3-button navigation. This offers split handler the opportunity to + // handle split to pip animation. + if (mPipHandler.isEnteringPip(change, info.getType()) + && mSplitHandler.getSplitItemPosition(change.getLastParent()) + != SPLIT_POSITION_UNDEFINED) { + return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + } + } + + // Split-screen is only interested in the recents transition finishing (and merging), so + // just wrap finish and start recents animation directly. + Transitions.TransitionFinishCallback finishCB = (wct) -> { + mInFlightSubAnimations = 0; + // If pair-to-pair switching, the post-recents clean-up isn't needed. + wct = wct != null ? wct : new WindowContainerTransaction(); + if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { + mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); + } else { + // notify pair-to-pair recents animation finish + mSplitHandler.onRecentsPairToPairAnimationFinish(wct); + } + mSplitHandler.onTransitionAnimationComplete(); + finishCallback.onTransitionFinished(wct); + }; + mInFlightSubAnimations = 1; + mSplitHandler.onRecentsInSplitAnimationStart(info); + final boolean handled = mLeftoversHandler.startAnimation( + mTransition, info, startTransaction, finishTransaction, finishCB); + if (!handled) { + mSplitHandler.onRecentsInSplitAnimationCanceled(); + } + return handled; + } + + @Override + void mergeAnimation( + @NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + switch (mType) { + case TYPE_RECENTS_DURING_DESKTOP: + mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + case TYPE_RECENTS_DURING_KEYGUARD: + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { + handoverTransitionLeashes(mInfo, info, t, mFinishT); + if (animateKeyguard( + this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) { + finishCallback.onTransitionFinished(null); + } + } + mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); + return; + case TYPE_RECENTS_DURING_SPLIT: + if (mSplitHandler.isPendingEnter(transition)) { + // Recents -> enter-split means that we are switching from one pair to + // another pair. + mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR; + } + mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + return; + default: + throw new IllegalStateException("Playing a Recents mixed transition with unknown or" + + " illegal type: " + mType); + } + } + + @Override + void onTransitionConsumed( + @NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishT) { + switch (mType) { + case TYPE_RECENTS_DURING_DESKTOP: + case TYPE_RECENTS_DURING_SPLIT: + mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); + break; + default: + break; + } + + if (mHasRequestToRemote) { + mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT); + } + } +} |