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 
21 import android.view.IRecentsAnimationController;
22 import android.view.SurfaceControl;
23 import android.window.PictureInPictureSurfaceTransaction;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.UiThread;
27 
28 import com.android.launcher3.util.Preconditions;
29 import com.android.launcher3.util.RunnableList;
30 import com.android.systemui.shared.recents.model.ThumbnailData;
31 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
32 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
33 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
34 
35 import java.util.function.Consumer;
36 
37 /**
38  * Wrapper around RecentsAnimationControllerCompat to help with some synchronization
39  */
40 public class RecentsAnimationController {
41 
42     private final RecentsAnimationControllerCompat mController;
43     private final Consumer<RecentsAnimationController> mOnFinishedListener;
44     private final boolean mAllowMinimizeSplitScreen;
45 
46     private boolean mUseLauncherSysBarFlags = false;
47     private boolean mSplitScreenMinimized = false;
48     private boolean mFinishRequested = false;
49     // Only valid when mFinishRequested == true.
50     private boolean mFinishTargetIsLauncher;
51     private RunnableList mPendingFinishCallbacks = new RunnableList();
52 
RecentsAnimationController(RecentsAnimationControllerCompat controller, boolean allowMinimizeSplitScreen, Consumer<RecentsAnimationController> onFinishedListener)53     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
54             boolean allowMinimizeSplitScreen,
55             Consumer<RecentsAnimationController> onFinishedListener) {
56         mController = controller;
57         mOnFinishedListener = onFinishedListener;
58         mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
59     }
60 
61     /**
62      * Synchronously takes a screenshot of the task with the given {@param taskId} if the task is
63      * currently being animated.
64      */
screenshotTask(int taskId)65     public ThumbnailData screenshotTask(int taskId) {
66         return mController.screenshotTask(taskId);
67     }
68 
69     /**
70      * Indicates that the gesture has crossed the window boundary threshold and system UI can be
71      * update the system bar flags accordingly.
72      */
setUseLauncherSystemBarFlags(boolean useLauncherSysBarFlags)73     public void setUseLauncherSystemBarFlags(boolean useLauncherSysBarFlags) {
74         if (mUseLauncherSysBarFlags != useLauncherSysBarFlags) {
75             mUseLauncherSysBarFlags = useLauncherSysBarFlags;
76             UI_HELPER_EXECUTOR.execute(() -> {
77                 mController.setAnimationTargetsBehindSystemBars(!useLauncherSysBarFlags);
78             });
79         }
80     }
81 
82     /**
83      * Indicates that the gesture has crossed the window boundary threshold and we should minimize
84      * if we are in splitscreen.
85      */
setSplitScreenMinimized(boolean splitScreenMinimized)86     public void setSplitScreenMinimized(boolean splitScreenMinimized) {
87         if (!mAllowMinimizeSplitScreen) {
88             return;
89         }
90         if (mSplitScreenMinimized != splitScreenMinimized) {
91             mSplitScreenMinimized = splitScreenMinimized;
92             UI_HELPER_EXECUTOR.execute(() -> {
93                 SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate();
94                 if (p != null) {
95                     p.setSplitScreenMinimized(splitScreenMinimized);
96                 }
97             });
98         }
99     }
100 
101     /**
102      * Remove task remote animation target from
103      * {@link RecentsAnimationCallbacks#onTaskAppeared(RemoteAnimationTargetCompat)}}.
104      */
105     @UiThread
removeTaskTarget(@onNull RemoteAnimationTargetCompat target)106     public void removeTaskTarget(@NonNull RemoteAnimationTargetCompat target) {
107         UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(target.taskId));
108     }
109 
110     @UiThread
finishAnimationToHome()111     public void finishAnimationToHome() {
112         finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
113     }
114 
115     @UiThread
finishAnimationToApp()116     public void finishAnimationToApp() {
117         finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
118     }
119 
120     /** See {@link #finish(boolean, Runnable, boolean)} */
121     @UiThread
finish(boolean toRecents, Runnable onFinishComplete)122     public void finish(boolean toRecents, Runnable onFinishComplete) {
123         finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
124     }
125 
126     /**
127      * @param onFinishComplete A callback that runs on the main thread after the animation
128      *                         controller has finished on the background thread.
129      * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
130      *                          activity. If userLeaveHint is true, the activity will enter into
131      *                          picture-in-picture mode upon being paused.
132      */
133     @UiThread
finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint)134     public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
135         Preconditions.assertUIThread();
136         finishController(toRecents, onFinishComplete, sendUserLeaveHint);
137     }
138 
139     @UiThread
finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint)140     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
141         if (mFinishRequested) {
142             // If finishing, add to pending finish callbacks, otherwise, if finished, adding to the
143             // destroyed RunnableList will just trigger the callback to be called immediately
144             mPendingFinishCallbacks.add(callback);
145             return;
146         }
147 
148         // Finish not yet requested
149         mFinishRequested = true;
150         mFinishTargetIsLauncher = toRecents;
151         mOnFinishedListener.accept(this);
152         mPendingFinishCallbacks.add(callback);
153         UI_HELPER_EXECUTOR.execute(() -> {
154             mController.finish(toRecents, sendUserLeaveHint);
155             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
156             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
157             MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
158         });
159     }
160 
161     /**
162      * @see IRecentsAnimationController#cleanupScreenshot()
163      */
164     @UiThread
cleanupScreenshot()165     public void cleanupScreenshot() {
166         UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
167     }
168 
169     /**
170      * @see RecentsAnimationControllerCompat#detachNavigationBarFromApp
171      */
172     @UiThread
detachNavigationBarFromApp(boolean moveHomeToTop)173     public void detachNavigationBarFromApp(boolean moveHomeToTop) {
174         UI_HELPER_EXECUTOR.execute(() -> mController.detachNavigationBarFromApp(moveHomeToTop));
175     }
176 
177     /**
178      * @see IRecentsAnimationController#animateNavigationBarToApp(long)
179      */
180     @UiThread
animateNavigationBarToApp(long duration)181     public void animateNavigationBarToApp(long duration) {
182         UI_HELPER_EXECUTOR.execute(() -> mController.animateNavigationBarToApp(duration));
183     }
184 
185     /**
186      * @see IRecentsAnimationController#setWillFinishToHome(boolean)
187      */
188     @UiThread
setWillFinishToHome(boolean willFinishToHome)189     public void setWillFinishToHome(boolean willFinishToHome) {
190         UI_HELPER_EXECUTOR.execute(() -> mController.setWillFinishToHome(willFinishToHome));
191     }
192 
193     /**
194      * Sets the final surface transaction on a Task. This is used by Launcher to notify the system
195      * that animating Activity to PiP has completed and the associated task surface should be
196      * updated accordingly. This should be called before `finish`
197      * @param taskId for which the leash should be updated
198      * @param finishTransaction the transaction to transfer to the task surface control after the
199      *                          leash is removed
200      * @param overlay the surface control for an overlay being shown above the pip (can be null)
201      */
setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)202     public void setFinishTaskTransaction(int taskId,
203             PictureInPictureSurfaceTransaction finishTransaction,
204             SurfaceControl overlay) {
205         UI_HELPER_EXECUTOR.execute(
206                 () -> mController.setFinishTaskTransaction(taskId, finishTransaction, overlay));
207     }
208 
209     /**
210      * Enables the input consumer to start intercepting touches in the app window.
211      */
enableInputConsumer()212     public void enableInputConsumer() {
213         UI_HELPER_EXECUTOR.submit(() -> {
214             mController.hideCurrentInputMethod();
215             mController.setInputConsumerEnabled(true);
216         });
217     }
218 
219     /** @return wrapper controller. */
getController()220     public RecentsAnimationControllerCompat getController() {
221         return mController;
222     }
223 
224     /**
225      * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
226      * the animation was finished to launcher vs an app.
227      */
getFinishTargetIsLauncher()228     public boolean getFinishTargetIsLauncher() {
229         return mFinishTargetIsLauncher;
230     }
231 }
232