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.Bundle;
25 import android.os.RemoteException;
26 import android.util.Log;
27 import android.view.IRecentsAnimationController;
28 import android.view.SurfaceControl;
29 import android.view.WindowManagerGlobal;
30 import android.window.PictureInPictureSurfaceTransaction;
31 
32 import androidx.annotation.UiThread;
33 
34 import com.android.internal.jank.Cuj;
35 import com.android.internal.os.IResultReceiver;
36 import com.android.launcher3.util.Preconditions;
37 import com.android.launcher3.util.RunnableList;
38 import com.android.quickstep.util.ActiveGestureErrorDetector;
39 import com.android.quickstep.util.ActiveGestureLog;
40 import com.android.systemui.shared.recents.model.ThumbnailData;
41 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
42 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
43 
44 import java.io.PrintWriter;
45 import java.util.function.Consumer;
46 
47 /**
48  * Wrapper around RecentsAnimationControllerCompat to help with some synchronization
49  */
50 public class RecentsAnimationController {
51 
52     private static final String TAG = "RecentsAnimationController";
53     private final RecentsAnimationControllerCompat mController;
54     private final Consumer<RecentsAnimationController> mOnFinishedListener;
55     private final boolean mAllowMinimizeSplitScreen;
56 
57     private boolean mUseLauncherSysBarFlags = false;
58     private boolean mSplitScreenMinimized = false;
59     private boolean mFinishRequested = false;
60     // Only valid when mFinishRequested == true.
61     private boolean mFinishTargetIsLauncher;
62     private RunnableList mPendingFinishCallbacks = new RunnableList();
63 
RecentsAnimationController(RecentsAnimationControllerCompat controller, boolean allowMinimizeSplitScreen, Consumer<RecentsAnimationController> onFinishedListener)64     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
65             boolean allowMinimizeSplitScreen,
66             Consumer<RecentsAnimationController> onFinishedListener) {
67         mController = controller;
68         mOnFinishedListener = onFinishedListener;
69         mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
70     }
71 
72     /**
73      * Synchronously takes a screenshot of the task with the given {@param taskId} if the task is
74      * currently being animated.
75      */
screenshotTask(int taskId)76     public ThumbnailData screenshotTask(int taskId) {
77         return mController.screenshotTask(taskId);
78     }
79 
80     /**
81      * Indicates that the gesture has crossed the window boundary threshold and system UI can be
82      * update the system bar flags accordingly.
83      */
setUseLauncherSystemBarFlags(boolean useLauncherSysBarFlags)84     public void setUseLauncherSystemBarFlags(boolean useLauncherSysBarFlags) {
85         if (mUseLauncherSysBarFlags != useLauncherSysBarFlags) {
86             mUseLauncherSysBarFlags = useLauncherSysBarFlags;
87             UI_HELPER_EXECUTOR.execute(() -> {
88                 if (!ENABLE_SHELL_TRANSITIONS) {
89                     mController.setAnimationTargetsBehindSystemBars(!useLauncherSysBarFlags);
90                 } else {
91                     try {
92                         WindowManagerGlobal.getWindowManagerService().setRecentsAppBehindSystemBars(
93                                 useLauncherSysBarFlags);
94                     } catch (RemoteException e) {
95                         Log.e(TAG, "Unable to reach window manager", e);
96                     }
97                 }
98             });
99         }
100     }
101 
102     /**
103      * Indicates that the gesture has crossed the window boundary threshold and we should minimize
104      * if we are in splitscreen.
105      */
setSplitScreenMinimized(Context context, boolean splitScreenMinimized)106     public void setSplitScreenMinimized(Context context, boolean splitScreenMinimized) {
107         if (!mAllowMinimizeSplitScreen) {
108             return;
109         }
110         if (mSplitScreenMinimized != splitScreenMinimized) {
111             mSplitScreenMinimized = splitScreenMinimized;
112         }
113     }
114 
115     /**
116      * Remove task remote animation target from
117      * {@link RecentsAnimationCallbacks#onTasksAppeared}}.
118      */
119     @UiThread
removeTaskTarget(int targetTaskId)120     public void removeTaskTarget(int targetTaskId) {
121         UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(targetTaskId));
122     }
123 
124     @UiThread
finishAnimationToHome()125     public void finishAnimationToHome() {
126         finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
127     }
128 
129     @UiThread
finishAnimationToApp()130     public void finishAnimationToApp() {
131         finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
132     }
133 
134     /** See {@link #finish(boolean, Runnable, boolean)} */
135     @UiThread
finish(boolean toRecents, Runnable onFinishComplete)136     public void finish(boolean toRecents, Runnable onFinishComplete) {
137         finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
138     }
139 
140     /**
141      * @param onFinishComplete A callback that runs on the main thread after the animation
142      *                         controller has finished on the background thread.
143      * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
144      *                          activity. If userLeaveHint is true, the activity will enter into
145      *                          picture-in-picture mode upon being paused.
146      */
147     @UiThread
finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint)148     public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
149         Preconditions.assertUIThread();
150         finishController(toRecents, onFinishComplete, sendUserLeaveHint);
151     }
152 
153     @UiThread
finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint)154     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
155         finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
156     }
157 
158     @UiThread
finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint, boolean forceFinish)159     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
160             boolean forceFinish) {
161         mPendingFinishCallbacks.add(callback);
162         if (!forceFinish && mFinishRequested) {
163             // If finish has already been requested, then add the callback to the pending list.
164             // If already finished, then adding it to the destroyed RunnableList will just
165             // trigger the callback to be called immediately
166             return;
167         }
168         ActiveGestureLog.INSTANCE.addLog(
169                 /* event= */ "finishRecentsAnimation",
170                 /* extras= */ toRecents,
171                 /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
172         // Finish not yet requested
173         mFinishRequested = true;
174         mFinishTargetIsLauncher = toRecents;
175         mOnFinishedListener.accept(this);
176         Runnable finishCb = () -> {
177             mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
178                 @Override
179                 public void send(int i, Bundle bundle) throws RemoteException {
180                     ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback");
181                     MAIN_EXECUTOR.execute(() -> {
182                         mPendingFinishCallbacks.executeAllAndDestroy();
183                     });
184                 }
185             });
186             InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
187             InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
188             InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
189         };
190         if (forceFinish) {
191             finishCb.run();
192         } else {
193             UI_HELPER_EXECUTOR.execute(finishCb);
194         }
195     }
196 
197     /**
198      * @see IRecentsAnimationController#cleanupScreenshot()
199      */
200     @UiThread
cleanupScreenshot()201     public void cleanupScreenshot() {
202         UI_HELPER_EXECUTOR.execute(() -> {
203             ActiveGestureLog.INSTANCE.addLog(
204                     "cleanupScreenshot",
205                     ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT);
206             mController.cleanupScreenshot();
207         });
208     }
209 
210     /**
211      * @see RecentsAnimationControllerCompat#detachNavigationBarFromApp
212      */
213     @UiThread
detachNavigationBarFromApp(boolean moveHomeToTop)214     public void detachNavigationBarFromApp(boolean moveHomeToTop) {
215         UI_HELPER_EXECUTOR.execute(() -> mController.detachNavigationBarFromApp(moveHomeToTop));
216     }
217 
218     /**
219      * @see IRecentsAnimationController#animateNavigationBarToApp(long)
220      */
221     @UiThread
animateNavigationBarToApp(long duration)222     public void animateNavigationBarToApp(long duration) {
223         UI_HELPER_EXECUTOR.execute(() -> mController.animateNavigationBarToApp(duration));
224     }
225 
226     /**
227      * @see IRecentsAnimationController#setWillFinishToHome(boolean)
228      */
229     @UiThread
setWillFinishToHome(boolean willFinishToHome)230     public void setWillFinishToHome(boolean willFinishToHome) {
231         UI_HELPER_EXECUTOR.execute(() -> mController.setWillFinishToHome(willFinishToHome));
232     }
233 
234     /**
235      * Sets the final surface transaction on a Task. This is used by Launcher to notify the system
236      * that animating Activity to PiP has completed and the associated task surface should be
237      * updated accordingly. This should be called before `finish`
238      * @param taskId for which the leash should be updated
239      * @param finishTransaction the transaction to transfer to the task surface control after the
240      *                          leash is removed
241      * @param overlay the surface control for an overlay being shown above the pip (can be null)
242      */
setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)243     public void setFinishTaskTransaction(int taskId,
244             PictureInPictureSurfaceTransaction finishTransaction,
245             SurfaceControl overlay) {
246         UI_HELPER_EXECUTOR.execute(
247                 () -> mController.setFinishTaskTransaction(taskId, finishTransaction, overlay));
248     }
249 
250     /**
251      * Enables the input consumer to start intercepting touches in the app window.
252      */
enableInputConsumer()253     public void enableInputConsumer() {
254         UI_HELPER_EXECUTOR.submit(() -> {
255             mController.setInputConsumerEnabled(true);
256         });
257     }
258 
259     /** @return wrapper controller. */
getController()260     public RecentsAnimationControllerCompat getController() {
261         return mController;
262     }
263 
264     /**
265      * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
266      * the animation was finished to launcher vs an app.
267      */
getFinishTargetIsLauncher()268     public boolean getFinishTargetIsLauncher() {
269         return mFinishTargetIsLauncher;
270     }
271 
dump(String prefix, PrintWriter pw)272     public void dump(String prefix, PrintWriter pw) {
273         pw.println(prefix + "RecentsAnimationController:");
274 
275         pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
276         pw.println(prefix + "\tmUseLauncherSysBarFlags=" + mUseLauncherSysBarFlags);
277         pw.println(prefix + "\tmSplitScreenMinimized=" + mSplitScreenMinimized);
278         pw.println(prefix + "\tmFinishRequested=" + mFinishRequested);
279         pw.println(prefix + "\tmFinishTargetIsLauncher=" + mFinishTargetIsLauncher);
280     }
281 }
282