• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep;
17 
18 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
19 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
20 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
21 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
22 
23 import android.content.Context;
24 import android.os.RemoteException;
25 import android.util.Log;
26 import android.view.IRecentsAnimationController;
27 import android.view.RemoteAnimationTarget;
28 import android.view.SurfaceControl;
29 import android.view.WindowManagerGlobal;
30 import android.window.PictureInPictureSurfaceTransaction;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.UiThread;
34 
35 import com.android.launcher3.util.Preconditions;
36 import com.android.launcher3.util.RunnableList;
37 import com.android.quickstep.util.ActiveGestureErrorDetector;
38 import com.android.quickstep.util.ActiveGestureLog;
39 import com.android.systemui.shared.recents.model.ThumbnailData;
40 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
41 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
42 
43 import java.util.function.Consumer;
44 
45 /**
46  * Wrapper around RecentsAnimationControllerCompat to help with some synchronization
47  */
48 public class RecentsAnimationController {
49 
50     private static final String TAG = "RecentsAnimationController";
51     private final RecentsAnimationControllerCompat mController;
52     private final Consumer<RecentsAnimationController> mOnFinishedListener;
53     private final boolean mAllowMinimizeSplitScreen;
54 
55     private boolean mUseLauncherSysBarFlags = false;
56     private boolean mSplitScreenMinimized = false;
57     private boolean mFinishRequested = false;
58     // Only valid when mFinishRequested == true.
59     private boolean mFinishTargetIsLauncher;
60     private RunnableList mPendingFinishCallbacks = new RunnableList();
61 
RecentsAnimationController(RecentsAnimationControllerCompat controller, boolean allowMinimizeSplitScreen, Consumer<RecentsAnimationController> onFinishedListener)62     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
63             boolean allowMinimizeSplitScreen,
64             Consumer<RecentsAnimationController> onFinishedListener) {
65         mController = controller;
66         mOnFinishedListener = onFinishedListener;
67         mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
68     }
69 
70     /**
71      * Synchronously takes a screenshot of the task with the given {@param taskId} if the task is
72      * currently being animated.
73      */
screenshotTask(int taskId)74     public ThumbnailData screenshotTask(int taskId) {
75         return mController.screenshotTask(taskId);
76     }
77 
78     /**
79      * Indicates that the gesture has crossed the window boundary threshold and system UI can be
80      * update the system bar flags accordingly.
81      */
setUseLauncherSystemBarFlags(boolean useLauncherSysBarFlags)82     public void setUseLauncherSystemBarFlags(boolean useLauncherSysBarFlags) {
83         if (mUseLauncherSysBarFlags != useLauncherSysBarFlags) {
84             mUseLauncherSysBarFlags = useLauncherSysBarFlags;
85             UI_HELPER_EXECUTOR.execute(() -> {
86                 if (!ENABLE_SHELL_TRANSITIONS) {
87                     mController.setAnimationTargetsBehindSystemBars(!useLauncherSysBarFlags);
88                 } else {
89                     try {
90                         WindowManagerGlobal.getWindowManagerService().setRecentsAppBehindSystemBars(
91                                 useLauncherSysBarFlags);
92                     } catch (RemoteException e) {
93                         Log.e(TAG, "Unable to reach window manager", e);
94                     }
95                 }
96             });
97         }
98     }
99 
100     /**
101      * Indicates that the gesture has crossed the window boundary threshold and we should minimize
102      * if we are in splitscreen.
103      */
setSplitScreenMinimized(Context context, boolean splitScreenMinimized)104     public void setSplitScreenMinimized(Context context, boolean splitScreenMinimized) {
105         if (!mAllowMinimizeSplitScreen) {
106             return;
107         }
108         if (mSplitScreenMinimized != splitScreenMinimized) {
109             mSplitScreenMinimized = splitScreenMinimized;
110         }
111     }
112 
113     /**
114      * Remove task remote animation target from
115      * {@link RecentsAnimationCallbacks#onTasksAppeared}}.
116      */
117     @UiThread
removeTaskTarget(int targetTaskId)118     public void removeTaskTarget(int targetTaskId) {
119         UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(targetTaskId));
120     }
121 
122     @UiThread
finishAnimationToHome()123     public void finishAnimationToHome() {
124         finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
125     }
126 
127     @UiThread
finishAnimationToApp()128     public void finishAnimationToApp() {
129         finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
130     }
131 
132     /** See {@link #finish(boolean, Runnable, boolean)} */
133     @UiThread
finish(boolean toRecents, Runnable onFinishComplete)134     public void finish(boolean toRecents, Runnable onFinishComplete) {
135         finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
136     }
137 
138     /**
139      * @param onFinishComplete A callback that runs on the main thread after the animation
140      *                         controller has finished on the background thread.
141      * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
142      *                          activity. If userLeaveHint is true, the activity will enter into
143      *                          picture-in-picture mode upon being paused.
144      */
145     @UiThread
finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint)146     public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
147         Preconditions.assertUIThread();
148         finishController(toRecents, onFinishComplete, sendUserLeaveHint);
149     }
150 
151     @UiThread
finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint)152     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
153         finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
154     }
155 
156     @UiThread
finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint, boolean forceFinish)157     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
158             boolean forceFinish) {
159         mPendingFinishCallbacks.add(callback);
160         if (!forceFinish && mFinishRequested) {
161             // If finish has already been requested, then add the callback to the pending list.
162             // If already finished, then adding it to the destroyed RunnableList will just
163             // trigger the callback to be called immediately
164             return;
165         }
166         ActiveGestureLog.INSTANCE.addLog(
167                 /* event= */ "finishRecentsAnimation",
168                 /* extras= */ toRecents,
169                 /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
170         // Finish not yet requested
171         mFinishRequested = true;
172         mFinishTargetIsLauncher = toRecents;
173         mOnFinishedListener.accept(this);
174         Runnable finishCb = () -> {
175             mController.finish(toRecents, sendUserLeaveHint);
176             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
177             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
178             InteractionJankMonitorWrapper.end(
179                     InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
180             MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
181         };
182         if (forceFinish) {
183             finishCb.run();
184         } else {
185             UI_HELPER_EXECUTOR.execute(finishCb);
186         }
187     }
188 
189     /**
190      * @see IRecentsAnimationController#cleanupScreenshot()
191      */
192     @UiThread
cleanupScreenshot()193     public void cleanupScreenshot() {
194         UI_HELPER_EXECUTOR.execute(() -> {
195             ActiveGestureLog.INSTANCE.addLog(
196                     "cleanupScreenshot",
197                     ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT);
198             mController.cleanupScreenshot();
199         });
200     }
201 
202     /**
203      * @see RecentsAnimationControllerCompat#detachNavigationBarFromApp
204      */
205     @UiThread
detachNavigationBarFromApp(boolean moveHomeToTop)206     public void detachNavigationBarFromApp(boolean moveHomeToTop) {
207         UI_HELPER_EXECUTOR.execute(() -> mController.detachNavigationBarFromApp(moveHomeToTop));
208     }
209 
210     /**
211      * @see IRecentsAnimationController#animateNavigationBarToApp(long)
212      */
213     @UiThread
animateNavigationBarToApp(long duration)214     public void animateNavigationBarToApp(long duration) {
215         UI_HELPER_EXECUTOR.execute(() -> mController.animateNavigationBarToApp(duration));
216     }
217 
218     /**
219      * @see IRecentsAnimationController#setWillFinishToHome(boolean)
220      */
221     @UiThread
setWillFinishToHome(boolean willFinishToHome)222     public void setWillFinishToHome(boolean willFinishToHome) {
223         UI_HELPER_EXECUTOR.execute(() -> mController.setWillFinishToHome(willFinishToHome));
224     }
225 
226     /**
227      * Sets the final surface transaction on a Task. This is used by Launcher to notify the system
228      * that animating Activity to PiP has completed and the associated task surface should be
229      * updated accordingly. This should be called before `finish`
230      * @param taskId for which the leash should be updated
231      * @param finishTransaction the transaction to transfer to the task surface control after the
232      *                          leash is removed
233      * @param overlay the surface control for an overlay being shown above the pip (can be null)
234      */
setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)235     public void setFinishTaskTransaction(int taskId,
236             PictureInPictureSurfaceTransaction finishTransaction,
237             SurfaceControl overlay) {
238         UI_HELPER_EXECUTOR.execute(
239                 () -> mController.setFinishTaskTransaction(taskId, finishTransaction, overlay));
240     }
241 
242     /**
243      * Enables the input consumer to start intercepting touches in the app window.
244      */
enableInputConsumer()245     public void enableInputConsumer() {
246         UI_HELPER_EXECUTOR.submit(() -> {
247             mController.setInputConsumerEnabled(true);
248         });
249     }
250 
251     /** @return wrapper controller. */
getController()252     public RecentsAnimationControllerCompat getController() {
253         return mController;
254     }
255 
256     /**
257      * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
258      * the animation was finished to launcher vs an app.
259      */
getFinishTargetIsLauncher()260     public boolean getFinishTargetIsLauncher() {
261         return mFinishTargetIsLauncher;
262     }
263 }
264