diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 62fb840d29d1f..ecdccd7b14195 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -34,6 +34,7 @@ import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.UnfoldTransitionHandler; @@ -131,6 +132,13 @@ public class ShellInitImpl { if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.register(mShellTaskOrganizer); mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init); + if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) { + final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions, + mPipTouchHandlerOptional.get().getTransitionHandler(), + mSplitScreenOptional.get().getTransitionHandler()); + // Added at end so that it has highest priority. + mTransitions.addHandler(mixedHandler); + } } // TODO(b/181599115): This should really be the pip controller, but until we can provide the 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 b6635f3b8dd7b..6d0aad09fcea0 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 @@ -295,6 +295,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, displayController.addDisplayWindowListener(this); } + public PipTransitionController getTransitionController() { + return mPipTransitionController; + } + public Rect getCurrentOrAnimatingBounds() { PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getCurrentAnimator(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 48df28ee4cdea..5d17f85bb2ca6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; @@ -52,6 +51,7 @@ import android.graphics.Rect; import android.os.IBinder; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; @@ -217,8 +217,9 @@ public class PipTransition extends PipTransitionController { } // Entering PIP. - if (isEnteringPip(info, mCurrentPipTaskToken)) { - return startEnterAnimation(info, startTransaction, finishTransaction, finishCallback); + if (isEnteringPip(info)) { + startEnterAnimation(info, startTransaction, finishTransaction, finishCallback); + return true; } // For transition that we don't animate, but contains the PIP leash, we need to update the @@ -245,22 +246,31 @@ public class PipTransition extends PipTransitionController { @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - if (request.getType() == TRANSIT_PIP) { + if (requestHasPipEnter(request)) { WindowContainerTransaction wct = new WindowContainerTransaction(); - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { - mRequestedEnterTransition = transition; - mRequestedEnterTask = request.getTriggerTask().token; - wct.setActivityWindowingMode(request.getTriggerTask().token, - WINDOWING_MODE_UNDEFINED); - final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); - wct.setBounds(request.getTriggerTask().token, destinationBounds); - } + augmentRequest(transition, request, wct); return wct; } else { return null; } } + @Override + public void augmentRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { + if (!requestHasPipEnter(request)) { + throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); + } + if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + mRequestedEnterTransition = transition; + mRequestedEnterTask = request.getTriggerTask().token; + outWCT.setActivityWindowingMode(request.getTriggerTask().token, + WINDOWING_MODE_UNDEFINED); + final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); + outWCT.setBounds(request.getTriggerTask().token, destinationBounds); + } + } + @Override public boolean handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct) { @@ -559,92 +569,94 @@ public class PipTransition extends PipTransitionController { } /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */ - private static boolean isEnteringPip(@NonNull TransitionInfo info, - @Nullable WindowContainerToken currentPipTaskToken) { + private boolean isEnteringPip(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getTaskInfo() != null - && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED - && !change.getContainer().equals(currentPipTaskToken)) { - // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps - // that enter PiP instantly on opening, mostly from CTS/Flicker tests) - if (info.getType() == TRANSIT_PIP || info.getType() == TRANSIT_OPEN) { - return true; - } - // This can happen if the request to enter PIP happens when we are collecting for - // another transition, such as TRANSIT_CHANGE (display rotation). - if (info.getType() == TRANSIT_CHANGE) { - return true; - } - - // Please file a bug to handle the unexpected transition type. - throw new IllegalStateException("Entering PIP with unexpected transition type=" - + transitTypeToString(info.getType())); - } + if (isEnteringPip(change, info.getType())) return true; } return false; } - private boolean startEnterAnimation(@NonNull TransitionInfo info, + /** Whether a particular change is a window that is entering pip. */ + @Override + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED + && !change.getContainer().equals(mCurrentPipTaskToken)) { + // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps + // that enter PiP instantly on opening, mostly from CTS/Flicker tests) + if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) { + return true; + } + // This can happen if the request to enter PIP happens when we are collecting for + // another transition, such as TRANSIT_CHANGE (display rotation). + if (transitType == TRANSIT_CHANGE) { + return true; + } + + // Please file a bug to handle the unexpected transition type. + throw new IllegalStateException("Entering PIP with unexpected transition type=" + + transitTypeToString(transitType)); + } + return false; + } + + private void startEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - // Search for an Enter PiP transition (along with a show wallpaper one) + // Search for an Enter PiP transition TransitionInfo.Change enterPip = null; - TransitionInfo.Change wallpaper = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.getTaskInfo() != null && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { enterPip = change; - } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - wallpaper = change; } } if (enterPip == null) { - return false; - } - // Keep track of the PIP task. - mCurrentPipTaskToken = enterPip.getContainer(); - mHasFadeOut = false; - - if (mFinishCallback != null) { - callFinishCallback(null /* wct */); - mFinishTransaction = null; - throw new RuntimeException("Previous callback not called, aborting entering PIP."); + throw new IllegalStateException("Trying to start PiP animation without a pip" + + "participant"); } - // Show the wallpaper if there is a wallpaper change. - if (wallpaper != null) { - startTransaction.show(wallpaper.getLeash()); - startTransaction.setAlpha(wallpaper.getLeash(), 1.f); - } // Make sure other open changes are visible as entering PIP. Some may be hidden in // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (change == enterPip || change == wallpaper) { - continue; - } + if (change == enterPip) continue; if (isOpeningType(change.getMode())) { final SurfaceControl leash = change.getLeash(); startTransaction.show(leash).setAlpha(leash, 1.f); } } + startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback); + } + + @Override + public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, + @NonNull final SurfaceControl.Transaction startTransaction, + @NonNull final SurfaceControl.Transaction finishTransaction, + @NonNull final Transitions.TransitionFinishCallback finishCallback) { + if (mFinishCallback != null) { + callFinishCallback(null /* wct */); + mFinishTransaction = null; + throw new RuntimeException("Previous callback not called, aborting entering PIP."); + } + + // Keep track of the PIP task and animation. + mCurrentPipTaskToken = pipChange.getContainer(); + mHasFadeOut = false; mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); mFinishCallback = finishCallback; mFinishTransaction = finishTransaction; - final int endRotation = mInFixedRotation ? mEndFixedRotation : enterPip.getEndRotation(); - return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(), - startTransaction, finishTransaction, enterPip.getStartRotation(), - endRotation); - } - private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash, - final SurfaceControl.Transaction startTransaction, - final SurfaceControl.Transaction finishTransaction, - final int startRotation, final int endRotation) { + final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo(); + final SurfaceControl leash = pipChange.getLeash(); + final int startRotation = pipChange.getStartRotation(); + final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation(); + setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, taskInfo.topActivityInfo); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); @@ -657,7 +669,6 @@ public class PipTransition extends PipTransitionController { computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, destinationBounds, sourceHintRect); } - PipAnimationController.PipTransitionAnimator animator; // Set corner radius for entering pip. mSurfaceTransactionHelper .crop(finishTransaction, leash, destinationBounds) @@ -694,7 +705,7 @@ public class PipTransition extends PipTransitionController { null /* callback */, false /* withStartDelay */); } mPipTransitionState.setInSwipePipToHomeTransition(false); - return true; + return; } if (rotationDelta != Surface.ROTATION_0) { @@ -702,6 +713,12 @@ public class PipTransition extends PipTransitionController { tmpTransform.postRotate(rotationDelta); startTransaction.setMatrix(leash, tmpTransform, new float[9]); } + if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + startTransaction.setAlpha(leash, 0f); + } + startTransaction.apply(); + + PipAnimationController.PipTransitionAnimator animator; if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, @@ -712,7 +729,6 @@ public class PipTransition extends PipTransitionController { animator.setUseContentOverlay(mContext); } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { - startTransaction.setAlpha(leash, 0f); animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); mOneShotAnimationType = ANIM_TYPE_BOUNDS; @@ -720,7 +736,6 @@ public class PipTransition extends PipTransitionController { throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType); } - startTransaction.apply(); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); @@ -731,8 +746,6 @@ public class PipTransition extends PipTransitionController { animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); } animator.start(); - - return true; } /** Computes destination bounds in old rotation and updates source hint rect if available. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 24993c621e3c1..28418be40c374 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -17,6 +17,7 @@ package com.android.wm.shell.pip; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.WindowManager.TRANSIT_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; @@ -28,10 +29,16 @@ import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; + import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.transition.Transitions; @@ -206,6 +213,30 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } + /** @return whether the transition-request represents a pip-entry. */ + public boolean requestHasPipEnter(@NonNull TransitionRequestInfo request) { + return request.getType() == TRANSIT_PIP; + } + + /** Whether a particular change is a window that is entering pip. */ + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + return false; + } + + /** Add PiP-related changes to `outWCT` for the given request. */ + public void augmentRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { + throw new IllegalStateException("Request isn't entering PiP"); + } + + /** Play a transition animation for entering PiP on a specific PiP change. */ + public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, + @NonNull final SurfaceControl.Transaction startTransaction, + @NonNull final SurfaceControl.Transaction finishTransaction, + @NonNull final Transitions.TransitionFinishCallback finishCallback) { + } + /** * Callback interface for PiP transitions (both from and to PiP mode) */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 147a272f46459..ba7a8361950e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -53,6 +53,7 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -249,6 +250,10 @@ public class PipTouchHandler { }); } + public PipTransitionController getTransitionHandler() { + return mPipTaskOrganizer.getTransitionController(); + } + private void reloadResources() { final Resources res = mContext.getResources(); mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer); 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 aec51baa4af75..028ae2a6423ab 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 @@ -108,7 +108,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, static final int EXIT_REASON_ROOT_TASK_VANISHED = 6; static final int EXIT_REASON_SCREEN_LOCKED = 7; static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8; - static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; + public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; @IntDef(value = { EXIT_REASON_UNKNOWN, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, @@ -198,6 +198,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.isSplitScreenVisible(); } + public StageCoordinator getTransitionHandler() { + return mStageCoordinator; + } + @Nullable public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) { if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) { 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 cd121ed41fdd4..4ff9d48a37075 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 @@ -21,7 +21,6 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; @@ -58,9 +57,6 @@ import java.util.ArrayList; class SplitScreenTransitions { private static final String TAG = "SplitScreenTransitions"; - /** Flag applied to a transition change to identify it as a divider bar for animation. */ - public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; - private final TransactionPool mTransactionPool; private final Transitions mTransitions; private final Runnable mOnFinish; @@ -187,27 +183,28 @@ class SplitScreenTransitions { } /** Starts a transition to dismiss split. */ - IBinder startDismissTransition(@Nullable IBinder transition, WindowContainerTransaction wct, + IBinder startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason) { final int type = reason == EXIT_REASON_DRAG_DIVIDER ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS; - if (transition == null) { - transition = mTransitions.startTransition(type, wct, handler); - } + IBinder transition = mTransitions.startTransition(type, wct, handler); + setDismissTransition(transition, dismissTop, reason); + return transition; + } + + /** Sets a transition to dismiss split. */ + void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop, + @SplitScreenController.ExitReason int reason) { mPendingDismiss = new DismissTransition(transition, reason, dismissTop); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Dismiss due to %s. toTop=%s", exitReasonToString(reason), stageTypeToString(dismissTop)); - return transition; } - IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct, - Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) { - if (transition == null) { - transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler); - } + void setRecentTransition(@NonNull IBinder transition, + @Nullable RemoteTransition remoteTransition) { mPendingRecent = transition; if (remoteTransition != null) { @@ -219,7 +216,6 @@ class SplitScreenTransitions { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter recent panel"); - return transition; } void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, 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 45931de5e35e1..96062f2110c74 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 @@ -28,6 +28,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; @@ -47,7 +48,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; -import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; @@ -126,12 +126,15 @@ import javax.inject.Provider; * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and * {@link #onStageHasChildrenChanged(StageListenerImpl).} */ -class StageCoordinator implements SplitLayout.SplitLayoutHandler, +public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler, ShellTaskOrganizer.TaskListener { private static final String TAG = StageCoordinator.class.getSimpleName(); + /** Flag applied to a transition change to identify it as a divider bar for animation. */ + public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; + private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final MainStage mMainStage; @@ -619,7 +622,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (ENABLE_SHELL_TRANSITIONS) { final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); - mSplitTransitions.startDismissTransition(null /* transition */, wct, this, + mSplitTransitions.startDismissTransition(wct, this, mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); } else { exitSplitScreen( @@ -649,8 +652,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(dismissTop, wct); - mSplitTransitions.startDismissTransition(null /* transition */, wct, this, - dismissTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + mSplitTransitions.startDismissTransition(wct, this, dismissTop, + EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } } } @@ -712,10 +715,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, .setWindowCrop(mSideStage.mRootLeash, null); }); - // Hide divider and reset its position. - mSplitLayout.resetDividerPosition(); - mSplitLayout.release(); - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + onTransitionAnimationComplete(); Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason)); // Log the exit if (childrenToTop != null) { @@ -1111,8 +1111,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(dismissTop, wct); - mSplitTransitions.startDismissTransition( - null /* transition */, wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER); + mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER); } @Override @@ -1301,7 +1300,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable TransitionRequestInfo request) { final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { - if (mMainStage.isActive()) { + if (isSplitActive()) { + // Check if the display is rotating. final TransitionRequestInfo.DisplayChange displayChange = request.getDisplayChange(); if (request.getType() == TRANSIT_CHANGE && displayChange != null @@ -1328,7 +1328,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - if (mMainStage.isActive()) { + if (isSplitActive()) { // Try to handle everything while in split-screen, so return a WCT even if it's empty. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" @@ -1343,7 +1343,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; prepareExitSplitScreen(dismissTop, out); - mSplitTransitions.startDismissTransition(transition, out, this, dismissTop, + mSplitTransitions.setDismissTransition(transition, dismissTop, EXIT_REASON_APP_FINISHED); } } else if (isOpening && inFullscreen) { @@ -1353,12 +1353,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. - mSplitTransitions.startRecentTransition(transition, out, this, + mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition()); } else { // Occluded by the other fullscreen task, so dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); - mSplitTransitions.startDismissTransition(transition, out, this, + mSplitTransitions.setDismissTransition(transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } @@ -1373,6 +1373,33 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return out; } + /** + * This is used for mixed scenarios. For such scenarios, just make sure to include exiting + * split or entering split when appropriate. + */ + public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request, + @NonNull WindowContainerTransaction outWCT) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + if (triggerTask != null && triggerTask.displayId != mDisplayId) { + // Skip handling task on the other display. + return; + } + final @WindowManager.TransitionType int type = request.getType(); + if (isSplitActive() && !isOpeningType(type) + && (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. " + + "mainStageCount=%d sideStageCount=%d", mMainStage.getChildCount(), + mSideStage.getChildCount()); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + } + } + + public boolean isSplitActive() { + return mMainStage.isActive(); + } + @Override public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, @@ -1465,7 +1492,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - void onTransitionAnimationComplete() { + /** Called to clean-up state and do house-keeping after the animation is done. */ + public void onTransitionAnimationComplete() { // If still playing, let it finish. if (!mMainStage.isActive()) { // Update divider state after animation so that it is still around and positioned @@ -1529,8 +1557,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - private boolean startPendingDismissAnimation( - @NonNull SplitScreenTransitions.DismissTransition dismissTransition, + /** 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, @NonNull SurfaceControl.Transaction finishT) { // Make some noise if things aren't totally expected. These states shouldn't effect @@ -1563,7 +1591,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again - if (shouldBreakPairedTaskInRecents(dismissTransition.mReason) && mShouldUpdateRecents) { + if (shouldBreakPairedTaskInRecents(dismissReason) && mShouldUpdateRecents) { for (TransitionInfo.Change change : info.getChanges()) { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo != null @@ -1580,30 +1608,38 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Wait until after animation to update divider // Reset crops so they don't interfere with subsequent launches - t.setWindowCrop(mMainStage.mRootLeash, null); - t.setWindowCrop(mSideStage.mRootLeash, null); + t.setCrop(mMainStage.mRootLeash, null); + t.setCrop(mSideStage.mRootLeash, null); - if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { - logExit(dismissTransition.mReason); - // TODO: Have a proper remote for this. Until then, though, reset state and use the - // normal animation stuff (which falls back to the normal launcher remote). - mSplitLayout.release(t); - mSplitTransitions.mPendingDismiss = null; - return false; + if (toStage == STAGE_TYPE_UNDEFINED) { + logExit(dismissReason); } else { - logExitToStage(dismissTransition.mReason, - dismissTransition.mDismissTop == STAGE_TYPE_MAIN); + logExitToStage(dismissReason, toStage == STAGE_TYPE_MAIN); } addDividerBarToTransition(info, t, false /* show */); - // We're dismissing split by moving the other one to fullscreen. - // Since we don't have any animations for this yet, just use the internal example - // animations. // Hide divider and dim layer on transition finished. setDividerVisibility(false, finishT); finishT.hide(mMainStage.mDimLayer); finishT.hide(mSideStage.mDimLayer); + } + + private boolean startPendingDismissAnimation( + @NonNull SplitScreenTransitions.DismissTransition dismissTransition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction finishT) { + prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info, + t, finishT); + if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { + // TODO: Have a proper remote for this. Until then, though, reset state and use the + // normal animation stuff (which falls back to the normal launcher remote). + t.hide(mSplitLayout.getDividerLeash()); + mSplitLayout.release(t); + mSplitTransitions.mPendingDismiss = null; + return false; + } + return true; } @@ -1776,7 +1812,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); - mSplitTransitions.startDismissTransition(null /* transition */, wct, + mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, STAGE_TYPE_UNDEFINED, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 9fd5d20038733..4b12eb86b5683 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -268,13 +268,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenTaskInfo.remove(taskId); mChildrenLeashes.remove(taskId); mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); - if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { - mCallbacks.onChildTaskEnterPip(taskId); - } if (ENABLE_SHELL_TRANSITIONS) { // Status is managed/synchronized by the transition lifecycle. return; } + if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + mCallbacks.onChildTaskEnterPip(taskId); + } sendStatusChanged(); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 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 new file mode 100644 index 0000000000000..1ffe26df729fe --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2022 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.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.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.splitscreen.StageCoordinator; + +import java.util.ArrayList; + +/** + * A handler for dealing with transitions involving multiple other handlers. For example: an + * activity in split-screen going into PiP. + */ +public class DefaultMixedHandler implements Transitions.TransitionHandler { + + private final Transitions mPlayer; + private final PipTransitionController mPipHandler; + private final StageCoordinator mSplitHandler; + + private static class MixedTransition { + static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; + + final int mType; + final IBinder mTransition; + + Transitions.TransitionFinishCallback mFinishCallback = null; + + /** + * Mixed transitions are made up of multiple "parts". This keeps track of how many + * parts are currently animating. + */ + int mInFlightSubAnimations = 0; + + MixedTransition(int type, IBinder transition) { + mType = type; + mTransition = transition; + } + } + private final ArrayList mActiveTransitions = new ArrayList<>(); + + public DefaultMixedHandler(@NonNull Transitions player, + @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { + mPlayer = player; + mPipHandler = pipHandler; + mSplitHandler = splitHandler; + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while " + + "Split-Screen is active, so treat it as Mixed."); + if (request.getRemoteTransition() != null) { + throw new IllegalStateException("Unexpected remote transition in" + + "pip-enter-from-split request"); + } + mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, + transition)); + + WindowContainerTransaction out = new WindowContainerTransaction(); + mPipHandler.augmentRequest(transition, request, out); + mSplitHandler.addEnterOrExitIfNeeded(request, out); + return out; + } + return null; + } + + private TransitionInfo subCopy(@NonNull TransitionInfo info, + @WindowManager.TransitionType int newType) { + final TransitionInfo out = new TransitionInfo(newType, info.getFlags()); + for (int i = 0; i < info.getChanges().size(); ++i) { + out.getChanges().add(info.getChanges().get(i)); + } + out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y); + out.setAnimationOptions(info.getAnimationOptions()); + return out; + } + + private boolean isHomeOpening(@NonNull TransitionInfo.Change change) { + return change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME; + } + + private 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, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + MixedTransition mixed = null; + for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { + if (mActiveTransitions.get(i).mTransition != transition) continue; + mixed = mActiveTransitions.remove(i); + break; + } + if (mixed == null) return false; + + if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { + return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, + finishCallback); + } else { + throw new IllegalStateException("Starting mixed animation without a known mixed type? " + + mixed.mType); + } + } + + private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed, + @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 while Split-Screen is active."); + TransitionInfo.Change pipChange = null; + TransitionInfo.Change wallpaper = null; + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK); + boolean homeIsOpening = false; + 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); + } 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; + mixed.mFinishCallback = finishCallback; + Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + --mixed.mInFlightSubAnimations; + if (mixed.mInFlightSubAnimations > 0) return; + if (isGoingHome) { + mSplitHandler.onTransitionAnimationComplete(); + } + mixed.mFinishCallback.onTransitionFinished(wct, wctCB); + }; + if (isGoingHome) { + 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(); + // Let split update internal state for dismiss. + mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + 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; + } + } + + mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, + finishCB); + // Dispatch the rest of the transition normally. This will most-likely be taken by + // recents or default handler. + mPlayer.dispatchTransition(mixed.mTransition, everythingElse, otherStartT, + finishTransaction, finishCB, this); + } 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; + mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, + finishCB); + } + return true; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + @Override + public void onTransitionMerged(@NonNull IBinder transition) { + MixedTransition mixed = null; + for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { + if (mActiveTransitions.get(i).mTransition != transition) continue; + mixed = mActiveTransitions.remove(i); + break; + } + if (mixed == null) return; + if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { + mPipHandler.onTransitionMerged(transition); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 435d67087f344..e4c94264be2c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -435,33 +435,42 @@ public class Transitions implements RemoteCallable { playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb)); } - boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) { - return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT, - (wct, cb) -> onFinish(active.mToken, wct, cb)); - } - - void playTransition(@NonNull ActiveTransition active) { + private void playTransition(@NonNull ActiveTransition active) { setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT); // If a handler already chose to run this animation, try delegating to it first. if (active.mHandler != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", active.mHandler); - if (startAnimation(active, active.mHandler)) { + boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo, + active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb)); + if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); return; } } - // Otherwise give every other handler a chance (in order) + // Otherwise give every other handler a chance + active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT, + active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler); + } + + /** + * Gives every handler (in order) a chance to animate until one consumes the transition. + * @return the handler which consumed the transition. + */ + TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, + @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) { for (int i = mHandlers.size() - 1; i >= 0; --i) { - if (mHandlers.get(i) == active.mHandler) continue; + if (mHandlers.get(i) == skip) continue; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s", mHandlers.get(i)); - if (startAnimation(active, mHandlers.get(i))) { + boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT, + finishCB); + if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", mHandlers.get(i)); - active.mHandler = mHandlers.get(i); - return; + return mHandlers.get(i); } } throw new IllegalStateException( 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 f847e6eb5e968..54b3f07ea9cc8 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 @@ -333,7 +333,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0); info.addChange(mainChange); info.addChange(sideChange); - IBinder transition = mSplitScreenTransitions.startDismissTransition(null, + IBinder transition = mSplitScreenTransitions.startDismissTransition( new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE); boolean accepted = mStageCoordinator.startAnimation(transition, info, @@ -355,7 +355,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0); info.addChange(mainChange); info.addChange(sideChange); - IBinder transition = mSplitScreenTransitions.startDismissTransition(null, + IBinder transition = mSplitScreenTransitions.startDismissTransition( new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER, STAGE_TYPE_SIDE); mMainStage.onTaskVanished(mMainChild);