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