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(@onNull RemoteAnimationTarget target)118 public void removeTaskTarget(@NonNull RemoteAnimationTarget target) { 119 UI_HELPER_EXECUTOR.execute(() -> mController.removeTask(target.taskId)); 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 if (mFinishRequested) { 154 // If finishing, add to pending finish callbacks, otherwise, if finished, adding to the 155 // destroyed RunnableList will just trigger the callback to be called immediately 156 mPendingFinishCallbacks.add(callback); 157 return; 158 } 159 ActiveGestureLog.INSTANCE.addLog( 160 /* event= */ "finishRecentsAnimation", 161 /* extras= */ toRecents, 162 /* gestureEvent= */ FINISH_RECENTS_ANIMATION); 163 164 // Finish not yet requested 165 mFinishRequested = true; 166 mFinishTargetIsLauncher = toRecents; 167 mOnFinishedListener.accept(this); 168 mPendingFinishCallbacks.add(callback); 169 UI_HELPER_EXECUTOR.execute(() -> { 170 mController.finish(toRecents, sendUserLeaveHint); 171 InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH); 172 InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME); 173 InteractionJankMonitorWrapper.end( 174 InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS); 175 MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy); 176 }); 177 } 178 179 /** 180 * @see IRecentsAnimationController#cleanupScreenshot() 181 */ 182 @UiThread cleanupScreenshot()183 public void cleanupScreenshot() { 184 UI_HELPER_EXECUTOR.execute(() -> { 185 ActiveGestureLog.INSTANCE.addLog( 186 "cleanupScreenshot", 187 ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT); 188 mController.cleanupScreenshot(); 189 }); 190 } 191 192 /** 193 * @see RecentsAnimationControllerCompat#detachNavigationBarFromApp 194 */ 195 @UiThread detachNavigationBarFromApp(boolean moveHomeToTop)196 public void detachNavigationBarFromApp(boolean moveHomeToTop) { 197 UI_HELPER_EXECUTOR.execute(() -> mController.detachNavigationBarFromApp(moveHomeToTop)); 198 } 199 200 /** 201 * @see IRecentsAnimationController#animateNavigationBarToApp(long) 202 */ 203 @UiThread animateNavigationBarToApp(long duration)204 public void animateNavigationBarToApp(long duration) { 205 UI_HELPER_EXECUTOR.execute(() -> mController.animateNavigationBarToApp(duration)); 206 } 207 208 /** 209 * @see IRecentsAnimationController#setWillFinishToHome(boolean) 210 */ 211 @UiThread setWillFinishToHome(boolean willFinishToHome)212 public void setWillFinishToHome(boolean willFinishToHome) { 213 UI_HELPER_EXECUTOR.execute(() -> mController.setWillFinishToHome(willFinishToHome)); 214 } 215 216 /** 217 * Sets the final surface transaction on a Task. This is used by Launcher to notify the system 218 * that animating Activity to PiP has completed and the associated task surface should be 219 * updated accordingly. This should be called before `finish` 220 * @param taskId for which the leash should be updated 221 * @param finishTransaction the transaction to transfer to the task surface control after the 222 * leash is removed 223 * @param overlay the surface control for an overlay being shown above the pip (can be null) 224 */ setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay)225 public void setFinishTaskTransaction(int taskId, 226 PictureInPictureSurfaceTransaction finishTransaction, 227 SurfaceControl overlay) { 228 UI_HELPER_EXECUTOR.execute( 229 () -> mController.setFinishTaskTransaction(taskId, finishTransaction, overlay)); 230 } 231 232 /** 233 * Enables the input consumer to start intercepting touches in the app window. 234 */ enableInputConsumer()235 public void enableInputConsumer() { 236 UI_HELPER_EXECUTOR.submit(() -> { 237 mController.setInputConsumerEnabled(true); 238 }); 239 } 240 241 /** @return wrapper controller. */ getController()242 public RecentsAnimationControllerCompat getController() { 243 return mController; 244 } 245 246 /** 247 * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether 248 * the animation was finished to launcher vs an app. 249 */ getFinishTargetIsLauncher()250 public boolean getFinishTargetIsLauncher() { 251 return mFinishTargetIsLauncher; 252 } 253 } 254