summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson Chung <winsonc@google.com> 2024-09-17 20:55:58 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-09-17 20:55:58 +0000
commit08718392053189ff52cb4ec575ec00746636be4c (patch)
tree1f9df0fb779639bd5251caac732d3ed0ac772e1d
parent85c2696b89da43b7e1c8ec67de646375f3ab68ce (diff)
parent4fb50c30398fbc6cb147d4c71e422e1f2d172a1a (diff)
Merge "Add simple mechanism for synthetic recents transitions" into main
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java246
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java177
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java2
8 files changed, 447 insertions, 57 deletions
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 9027bf34a58e..88878c6adcf2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -40,6 +40,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -339,6 +340,52 @@ public class TransitionUtil {
return target;
}
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change and leash
+ */
+ public static RemoteAnimationTarget newSyntheticTarget(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, @TransitionInfo.TransitionMode int mode, int order,
+ boolean isTranslucent) {
+ int taskId;
+ boolean isNotInRecents;
+ WindowConfiguration windowConfiguration;
+
+ if (taskInfo != null) {
+ taskId = taskInfo.taskId;
+ isNotInRecents = !taskInfo.isRunning;
+ windowConfiguration = taskInfo.configuration.windowConfiguration;
+ } else {
+ taskId = INVALID_TASK_ID;
+ isNotInRecents = true;
+ windowConfiguration = new WindowConfiguration();
+ }
+
+ Rect localBounds = new Rect();
+ RemoteAnimationTarget target = new RemoteAnimationTarget(
+ taskId,
+ newModeToLegacyMode(mode),
+ // TODO: once we can properly sync transactions across process,
+ // then get rid of this leash.
+ leash,
+ isTranslucent,
+ null,
+ // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+ new Rect(0, 0, 0, 0),
+ order,
+ null,
+ localBounds,
+ new Rect(),
+ windowConfiguration,
+ isNotInRecents,
+ null,
+ new Rect(),
+ taskInfo,
+ false,
+ INVALID_WINDOW_TYPE
+ );
+ return target;
+ }
+
private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
SurfaceControl leash) {
return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 452d12a242c0..7e6f43458ba6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -46,7 +46,6 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
-import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -55,7 +54,6 @@ import android.window.TaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.api.CompatUIHandler;
@@ -74,7 +72,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
/**
* Unified task organizer for all components in the shell.
@@ -561,19 +558,6 @@ public class ShellTaskOrganizer extends TaskOrganizer {
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
}
- /**
- * Take a screenshot of a task.
- */
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
- Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
- final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
- if (info == null) {
- return;
- }
- ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
- }
-
-
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b151c8b7e718..05a70d8ae197 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -487,10 +487,11 @@ public abstract class WMShellModule {
@Provides
static RecentsTransitionHandler provideRecentsTransitionHandler(
ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
Optional<RecentTasksController> recentTasksController,
HomeTransitionObserver homeTransitionObserver) {
- return new RecentsTransitionHandler(shellInit, transitions,
+ return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions,
recentTasksController.orElse(null), homeTransitionObserver);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index c660000e4f61..8077aeebf27f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -20,9 +20,12 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -41,6 +44,7 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -64,6 +68,7 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,10 +84,15 @@ import java.util.function.Consumer;
* Handles the Recents (overview) animation. Only one of these can run at a time. A recents
* transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
*/
-public class RecentsTransitionHandler implements Transitions.TransitionHandler {
+public class RecentsTransitionHandler implements Transitions.TransitionHandler,
+ Transitions.TransitionObserver {
private static final String TAG = "RecentsTransitionHandler";
+ // A placeholder for a synthetic transition that isn't backed by a true system transition
+ public static final IBinder SYNTHETIC_TRANSITION = new Binder();
+
private final Transitions mTransitions;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mExecutor;
@Nullable
private final RecentTasksController mRecentTasksController;
@@ -99,19 +109,26 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private final HomeTransitionObserver mHomeTransitionObserver;
private @Nullable Color mBackgroundColor;
- public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
+ public RecentsTransitionHandler(
+ @NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
@Nullable RecentTasksController recentTasksController,
- HomeTransitionObserver homeTransitionObserver) {
+ @NonNull HomeTransitionObserver homeTransitionObserver) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
mRecentTasksController = recentTasksController;
mHomeTransitionObserver = homeTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
- shellInit.addInitCallback(() -> {
- recentTasksController.setTransitionHandler(this);
- transitions.addHandler(this);
- }, this);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mRecentTasksController.setTransitionHandler(this);
+ mTransitions.addHandler(this);
+ mTransitions.registerObserver(this);
}
/** Register a mixer handler. {@see RecentsMixedHandler}*/
@@ -138,17 +155,59 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mBackgroundColor = color;
}
+ /**
+ * Starts a new real/synthetic recents transition.
+ */
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
+ // only care about latest one.
+ mAnimApp = appThread;
+
+ // TODO(b/366021931): Formalize this later
+ final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition");
+ if (isSyntheticRequest) {
+ return startSyntheticRecentsTransition(listener);
+ } else {
+ return startRealRecentsTransition(intent, fillIn, options, listener);
+ }
+ }
+
+ /**
+ * Starts a synthetic recents transition that is not backed by a real WM transition.
+ */
+ private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.startRecentsTransition(synthetic)");
+ final RecentsController lastController = getLastController();
+ if (lastController != null) {
+ lastController.cancel(lastController.isSyntheticTransition()
+ ? "existing_running_synthetic_transition"
+ : "existing_running_transition");
+ return null;
+ }
+
+ // Create a new synthetic transition and start it immediately
+ final RecentsController controller = new RecentsController(listener);
+ controller.startSyntheticTransition();
+ mControllers.add(controller);
+ return SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Starts a real WM-backed recents transition.
+ */
+ private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ IRecentsAnimationRunner listener) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startRecentsTransition");
- // only care about latest one.
- mAnimApp = appThread;
- WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.sendPendingIntent(intent, fillIn, options);
- final RecentsController controller = new RecentsController(listener);
+
+ // Find the mixed handler which should handle this request (if we are in a state where a
+ // mixed handler is needed). This is slightly convoluted because starting the transition
+ // requires the handler, but the mixed handler also needs a reference to the transition.
RecentsMixedHandler mixer = null;
Consumer<IBinder> setTransitionForMixer = null;
for (int i = 0; i < mMixers.size(); ++i) {
@@ -160,12 +219,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
mixer == null ? this : mixer);
- for (int i = 0; i < mStateListeners.size(); i++) {
- mStateListeners.get(i).onTransitionStarted(transition);
- }
if (mixer != null) {
setTransitionForMixer.accept(transition);
}
+
+ final RecentsController controller = new RecentsController(listener);
if (transition != null) {
controller.setTransition(transition);
mControllers.add(controller);
@@ -187,11 +245,28 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
return null;
}
- private int findController(IBinder transition) {
+ /**
+ * Returns if there is currently a pending or active recents transition.
+ */
+ @Nullable
+ private RecentsController getLastController() {
+ return !mControllers.isEmpty() ? mControllers.getLast() : null;
+ }
+
+ /**
+ * Finds an existing controller for the provided {@param transition}, or {@code null} if none
+ * exists.
+ */
+ @Nullable
+ @VisibleForTesting
+ RecentsController findController(@NonNull IBinder transition) {
for (int i = mControllers.size() - 1; i >= 0; --i) {
- if (mControllers.get(i).mTransition == transition) return i;
+ final RecentsController controller = mControllers.get(i);
+ if (controller.mTransition == transition) {
+ return controller;
+ }
}
- return -1;
+ return null;
}
@Override
@@ -199,13 +274,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
Transitions.TransitionFinishCallback finishCallback) {
- final int controllerIdx = findController(transition);
- if (controllerIdx < 0) {
+ final RecentsController controller = findController(transition);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startAnimation: no controller found");
return false;
}
- final RecentsController controller = mControllers.get(controllerIdx);
final IApplicationThread animApp = mAnimApp;
mAnimApp = null;
if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
@@ -221,13 +295,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
- final int targetIdx = findController(mergeTarget);
- if (targetIdx < 0) {
+ final RecentsController controller = findController(mergeTarget);
+ if (controller == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- final RecentsController controller = mControllers.get(targetIdx);
controller.merge(info, t, finishCallback);
}
@@ -244,8 +317,21 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ RecentsController controller = findController(SYNTHETIC_TRANSITION);
+ if (controller != null) {
+ // Cancel the existing synthetic transition if there is one
+ controller.cancel("incoming_transition");
+ }
+ }
+
/** There is only one of these and it gets reset on finish. */
- private class RecentsController extends IRecentsAnimationController.Stub {
+ @VisibleForTesting
+ class RecentsController extends IRecentsAnimationController.Stub {
+
private final int mInstanceId;
private IRecentsAnimationRunner mListener;
@@ -307,7 +393,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mDeathHandler = () -> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
- finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
+ finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
+ "deathRecipient");
};
try {
mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -317,6 +404,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ /**
+ * Sets the started transition for this instance of the recents transition.
+ */
void setTransition(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
@@ -330,6 +420,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
void cancel(boolean toHome, boolean withScreenshots, String reason) {
+ if (cancelSyntheticTransition(reason)) {
+ return;
+ }
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.cancel: toHome=%b reason=%s",
mInstanceId, toHome, reason);
@@ -341,7 +435,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
if (mFinishCB != null) {
- finishInner(toHome, false /* userLeave */, null /* finishCb */);
+ finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel");
} else {
cleanUp();
}
@@ -436,6 +530,91 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ /**
+ * Starts a new transition that is not backed by a system transition.
+ */
+ void startSyntheticTransition() {
+ mTransition = SYNTHETIC_TRANSITION;
+
+ // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as
+ // both opening and closing since there's some pre-existing
+ // dependencies on having a closing task
+ final ActivityManager.RunningTaskInfo homeTask =
+ mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream()
+ .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME)
+ .findFirst()
+ .get();
+ final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+ 0, true /* isTranslucent */);
+ final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
+ homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+ 0, true /* isTranslucent */);
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ apps.add(openingTarget);
+ apps.add(closingTarget);
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.start: calling onAnimationStart with %d apps",
+ mInstanceId, apps.size());
+ mListener.onAnimationStart(this,
+ apps.toArray(new RemoteAnimationTarget[apps.size()]),
+ new RemoteAnimationTarget[0],
+ new Rect(0, 0, 0, 0), new Rect(), new Bundle());
+ for (int i = 0; i < mStateListeners.size(); i++) {
+ mStateListeners.get(i).onAnimationStateChanged(true);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting recents animation", e);
+ cancel("startSynthetricTransition() failed");
+ }
+ }
+
+ /**
+ * Returns whether this transition is backed by a real system transition or not.
+ */
+ boolean isSyntheticTransition() {
+ return mTransition == SYNTHETIC_TRANSITION;
+ }
+
+ /**
+ * Called when a synthetic transition is canceled.
+ */
+ boolean cancelSyntheticTransition(String reason) {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.cancelSyntheticTransition reason=%s",
+ mInstanceId, reason);
+ try {
+ // TODO(b/366021931): Notify the correct tasks once we build actual targets, and
+ // clean up leashes accordingly
+ mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error canceling previous recents animation", e);
+ }
+ cleanUp();
+ return true;
+ }
+
+ /**
+ * Called when a synthetic transition is finished.
+ * @return
+ */
+ boolean finishSyntheticTransition() {
+ if (!isSyntheticTransition()) {
+ return false;
+ }
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishSyntheticTransition", mInstanceId);
+ // TODO(b/366021931): Clean up leashes accordingly
+ cleanUp();
+ return true;
+ }
+
boolean start(TransitionInfo info, SurfaceControl.Transaction t,
SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -662,7 +841,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
// Set the callback once again so we can finish correctly.
mFinishCB = finishCB;
finishInner(true /* toHome */, false /* userLeave */,
- null /* finishCb */);
+ null /* finishCb */, "takeOverAnimation");
}, updatedStates);
});
}
@@ -810,7 +989,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
sendCancelWithSnapshots();
mExecutor.executeDelayed(
() -> finishInner(true /* toHome */, false /* userLeaveHint */,
- null /* finishCb */), 0);
+ null /* finishCb */, "merge"), 0);
return;
}
if (recentsOpening != null) {
@@ -1005,7 +1184,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
return;
}
final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
- : Display.DEFAULT_DISPLAY;
+ : DEFAULT_DISPLAY;
// transient launches don't receive focus automatically. Since we are taking over
// the gesture now, take focus explicitly.
// This also moves recents back to top if the user gestured before a switch
@@ -1038,11 +1217,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
@Override
@SuppressLint("NewApi")
public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
- mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
+ mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb,
+ "requested"));
}
private void finishInner(boolean toHome, boolean sendUserLeaveHint,
- IResultReceiver runnerFinishCb) {
+ IResultReceiver runnerFinishCb, String reason) {
+ if (finishSyntheticTransition()) {
+ return;
+ }
+
if (mFinishCB == null) {
Slog.e(TAG, "Duplicate call to finish");
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
index e8733ebd8f03..95874c8193c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -24,7 +24,4 @@ public interface RecentsTransitionStateListener {
/** Notifies whether the recents animation is running. */
default void onAnimationStateChanged(boolean running) {
}
-
- /** Notifies that a recents shell transition has started. */
- default void onTransitionStarted(IBinder 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 2c02d4f8bd1a..d03832d3e85e 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
@@ -1501,16 +1501,16 @@ public class Transitions implements RemoteCallable<Transitions>,
* transition animation. The Transition system will apply it when
* finishCallback is called by the transition handler.
*/
- void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ default void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction);
+ @NonNull SurfaceControl.Transaction finishTransaction) {}
/**
* Called when the transition is starting to play. It isn't called for merged transitions.
*
* @param transition the unique token of this transition
*/
- void onTransitionStarting(@NonNull IBinder transition);
+ default void onTransitionStarting(@NonNull IBinder transition) {}
/**
* Called when a transition is merged into another transition. There won't be any following
@@ -1519,7 +1519,7 @@ public class Transitions implements RemoteCallable<Transitions>,
* @param merged the unique token of the transition that's merged to another one
* @param playing the unique token of the transition that accepts the merge
*/
- void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+ default void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}
/**
* Called when the transition is finished. This isn't called for merged transitions.
@@ -1527,7 +1527,7 @@ public class Transitions implements RemoteCallable<Transitions>,
* @param transition the unique token of this transition
* @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
*/
- void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+ default void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
}
@BinderThread
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
new file mode 100644
index 000000000000..769acf7fdfde
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2021 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.recents;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link RecentTasksController}
+ *
+ * Usage: atest WMShellUnitTests:RecentsTransitionHandlerTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RecentsTransitionHandlerTest extends ShellTestCase {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private TaskStackListenerImpl mTaskStackListener;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
+ private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ @Mock
+ private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
+ @Mock
+ private IRecentTasksListener mRecentTasksListener;
+ @Mock
+ private TaskStackTransitionObserver mTaskStackTransitionObserver;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ private RecentTasksController mRecentTasksController;
+ private RecentTasksController mRecentTasksControllerReal;
+ private RecentsTransitionHandler mRecentsTransitionHandler;
+ private ShellInit mShellInit;
+ private ShellController mShellController;
+ private TestShellExecutor mMainExecutor;
+ private static StaticMockitoSession sMockitoSession;
+
+ @Before
+ public void setUp() {
+ sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus.class).startMocking();
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+
+ mMainExecutor = new TestShellExecutor();
+ when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ when(mContext.getSystemService(KeyguardManager.class))
+ .thenReturn(mock(KeyguardManager.class));
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mMainExecutor));
+ mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
+ mMainExecutor);
+ mRecentTasksController = spy(mRecentTasksControllerReal);
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+ mMainExecutor);
+
+ final Transitions transitions = mock(Transitions.class);
+ doReturn(mMainExecutor).when(transitions).getMainExecutor();
+ mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer,
+ transitions, mRecentTasksController, mock(HomeTransitionObserver.class));
+
+ mShellInit.init();
+ }
+
+ @After
+ public void tearDown() {
+ sMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationStart() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ // Finish and verify no transition remains
+ mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
+ false /* sendUserLeaveHint */, null /* finishCb */);
+ mMainExecutor.flushAll();
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+
+ @Test
+ public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception {
+ final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
+ doReturn(new Binder()).when(runner).asBinder();
+ Bundle options = new Bundle();
+ options.putBoolean("is_synthetic_recents_transition", true);
+ IBinder transition = mRecentsTransitionHandler.startRecentsTransition(
+ mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class),
+ runner);
+ verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
+
+ mRecentsTransitionHandler.findController(transition).cancel("test");
+ mMainExecutor.flushAll();
+ verify(runner).onAnimationCanceled(any(), any());
+ assertNull(mRecentsTransitionHandler.findController(transition));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 61a725f5701d..fec9e3ebd1ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1211,7 +1211,7 @@ public class ShellTransitionTests extends ShellTestCase {
mTransactionPool, createTestDisplayController(), mMainExecutor,
mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
- new RecentsTransitionHandler(shellInit, transitions,
+ new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions,
mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();