Merge "Add a DefaultMixedHandler handler combined transitions" into tm-qpr-dev am: f7369c8bc4

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17621381

Change-Id: Ied412a43b6513cdb4138eec7257828824ba878d2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Evan Rosky
2022-04-12 19:55:43 +00:00
committed by Automerger Merge Worker
12 changed files with 497 additions and 138 deletions

View File

@@ -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

View File

@@ -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();

View File

@@ -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. */

View File

@@ -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)
*/

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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<MixedTransition> 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);
}
}
}

View File

@@ -435,33 +435,42 @@ public class Transitions implements RemoteCallable<Transitions> {
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(

View File

@@ -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);