diff options
| author | 2024-09-17 20:55:58 +0000 | |
|---|---|---|
| committer | 2024-09-17 20:55:58 +0000 | |
| commit | 08718392053189ff52cb4ec575ec00746636be4c (patch) | |
| tree | 1f9df0fb779639bd5251caac732d3ed0ac772e1d | |
| parent | 85c2696b89da43b7e1c8ec67de646375f3ab68ce (diff) | |
| parent | 4fb50c30398fbc6cb147d4c71e422e1f2d172a1a (diff) | |
Merge "Add simple mechanism for synthetic recents transitions" into main
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(); |