1 /* 2 * Copyright (C) 2019 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 android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 19 20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 21 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 22 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED; 23 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED; 24 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION; 25 import static com.android.systemui.shared.system.RemoteTransitionCompat.newRemoteTransition; 26 27 import android.app.ActivityManager; 28 import android.app.ActivityOptions; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.os.SystemProperties; 32 import android.util.Log; 33 import android.view.RemoteAnimationTarget; 34 import android.window.RemoteTransition; 35 36 import androidx.annotation.Nullable; 37 import androidx.annotation.UiThread; 38 39 import com.android.launcher3.Utilities; 40 import com.android.launcher3.config.FeatureFlags; 41 import com.android.quickstep.TopTaskTracker.CachedTaskInfo; 42 import com.android.quickstep.util.ActiveGestureLog; 43 import com.android.quickstep.views.DesktopTaskView; 44 import com.android.quickstep.views.RecentsView; 45 import com.android.systemui.shared.recents.model.ThumbnailData; 46 import com.android.systemui.shared.system.ActivityManagerWrapper; 47 import com.android.systemui.shared.system.TaskStackChangeListener; 48 import com.android.systemui.shared.system.TaskStackChangeListeners; 49 50 import java.util.HashMap; 51 52 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener { 53 public static final boolean ENABLE_SHELL_TRANSITIONS = 54 SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); 55 public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS 56 && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false); 57 58 private RecentsAnimationController mController; 59 private RecentsAnimationCallbacks mCallbacks; 60 private RecentsAnimationTargets mTargets; 61 // Temporary until we can hook into gesture state events 62 private GestureState mLastGestureState; 63 private RemoteAnimationTarget mLastAppearedTaskTarget; 64 private Runnable mLiveTileCleanUpHandler; 65 private Context mCtx; 66 67 private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() { 68 @Override 69 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 70 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 71 if (mLastGestureState == null) { 72 TaskStackChangeListeners.getInstance().unregisterTaskStackListener( 73 mLiveTileRestartListener); 74 return; 75 } 76 BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface(); 77 if (activityInterface.isInLiveTileMode() 78 && activityInterface.getCreatedActivity() != null) { 79 RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel(); 80 if (recentsView != null) { 81 recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId); 82 TaskStackChangeListeners.getInstance().unregisterTaskStackListener( 83 mLiveTileRestartListener); 84 } 85 } 86 } 87 }; 88 TaskAnimationManager(Context ctx)89 TaskAnimationManager(Context ctx) { 90 mCtx = ctx; 91 } 92 /** 93 * Preloads the recents animation. 94 */ preloadRecentsAnimation(Intent intent)95 public void preloadRecentsAnimation(Intent intent) { 96 // Pass null animation handler to indicate this start is for preloading 97 UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() 98 .startRecentsActivity(intent, 0, null, null, null)); 99 } 100 101 /** 102 * Starts a new recents animation for the activity with the given {@param intent}. 103 */ 104 @UiThread startRecentsAnimation(GestureState gestureState, Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener)105 public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState, 106 Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) { 107 ActiveGestureLog.INSTANCE.addLog( 108 /* event= */ "startRecentsAnimation", 109 /* gestureEvent= */ START_RECENTS_ANIMATION); 110 // Notify if recents animation is still running 111 if (mController != null) { 112 String msg = "New recents animation started before old animation completed"; 113 if (FeatureFlags.IS_STUDIO_BUILD) { 114 throw new IllegalArgumentException(msg); 115 } else { 116 Log.e("TaskAnimationManager", msg, new Exception()); 117 } 118 } 119 // But force-finish it anyways 120 finishRunningRecentsAnimation(false /* toHome */); 121 122 if (mCallbacks != null) { 123 // If mCallbacks still != null, that means we are getting this startRecentsAnimation() 124 // before the previous one got onRecentsAnimationStart(). In that case, cleanup the 125 // previous animation so it doesn't mess up/listen to state changes in this animation. 126 cleanUpRecentsAnimation(); 127 } 128 129 final BaseActivityInterface activityInterface = gestureState.getActivityInterface(); 130 mLastGestureState = gestureState; 131 mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx), 132 activityInterface.allowMinimizeSplitScreen()); 133 mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() { 134 @Override 135 public void onRecentsAnimationStart(RecentsAnimationController controller, 136 RecentsAnimationTargets targets) { 137 if (mCallbacks == null) { 138 // It's possible for the recents animation to have finished and be cleaned up 139 // by the time we process the start callback, and in that case, just we can skip 140 // handling this call entirely 141 return; 142 } 143 mController = controller; 144 mTargets = targets; 145 mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId()); 146 mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); 147 } 148 149 @Override 150 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 151 cleanUpRecentsAnimation(); 152 } 153 154 @Override 155 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 156 cleanUpRecentsAnimation(); 157 } 158 159 @Override 160 public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) { 161 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0]; 162 BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface(); 163 164 for (RemoteAnimationTarget compat : appearedTaskTargets) { 165 if (compat.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME 166 && activityInterface.getCreatedActivity() instanceof RecentsActivity) { 167 // When receive opening home activity while recents is running, enter home 168 // and dismiss recents. 169 ((RecentsActivity) activityInterface.getCreatedActivity()).startHome(); 170 return; 171 } 172 } 173 174 RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.get(mCtx) 175 .onStartingSplitLegacy(appearedTaskTargets); 176 if (nonAppTargets == null) { 177 nonAppTargets = new RemoteAnimationTarget[0]; 178 } 179 if (activityInterface.isInLiveTileMode() 180 && activityInterface.getCreatedActivity() != null) { 181 RecentsView recentsView = 182 activityInterface.getCreatedActivity().getOverviewPanel(); 183 if (recentsView != null) { 184 recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, 185 appearedTaskTargets, 186 new RemoteAnimationTarget[0] /* wallpaper */, 187 nonAppTargets /* nonApps */); 188 return; 189 } 190 } else if (nonAppTargets.length > 0) { 191 TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets /* nonApps */, 192 true /*shown*/, null /* animatorHandler */); 193 } 194 if (mController != null) { 195 if (mLastAppearedTaskTarget == null 196 || appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) { 197 if (mLastAppearedTaskTarget != null) { 198 mController.removeTaskTarget(mLastAppearedTaskTarget); 199 } 200 mLastAppearedTaskTarget = appearedTaskTarget; 201 mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); 202 } 203 } 204 } 205 206 @Override 207 public boolean onSwitchToScreenshot(Runnable onFinished) { 208 if (!activityInterface.isInLiveTileMode() 209 || activityInterface.getCreatedActivity() == null) { 210 // No need to switch since tile is already a screenshot. 211 onFinished.run(); 212 } else { 213 final RecentsView recentsView = 214 activityInterface.getCreatedActivity().getOverviewPanel(); 215 if (recentsView != null) { 216 recentsView.switchToScreenshot(onFinished); 217 } else { 218 onFinished.run(); 219 } 220 } 221 return true; 222 } 223 }); 224 final long eventTime = gestureState.getSwipeUpStartTimeMs(); 225 mCallbacks.addListener(gestureState); 226 mCallbacks.addListener(listener); 227 228 if (ENABLE_SHELL_TRANSITIONS) { 229 RemoteTransition transition = newRemoteTransition(mCallbacks, 230 mController != null ? mController.getController() : null, 231 mCtx.getIApplicationThread()); 232 final ActivityOptions options = ActivityOptions.makeRemoteTransition(transition); 233 // Allowing to pause Home if Home is top activity and Recents is not Home. So when user 234 // start home when recents animation is playing, the home activity can be resumed again 235 // to let the transition controller collect Home activity. 236 CachedTaskInfo cti = gestureState.getRunningTask(); 237 boolean homeIsOnTop = cti != null && cti.isHomeTask(); 238 if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) { 239 if (cti != null && cti.isFreeformTask()) { 240 // No transient launch when desktop task is on top 241 homeIsOnTop = true; 242 } 243 } 244 if (!homeIsOnTop) { 245 options.setTransientLaunch(); 246 } 247 options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime); 248 UI_HELPER_EXECUTOR.execute(() -> mCtx.startActivity(intent, options.toBundle())); 249 } else { 250 UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() 251 .startRecentsActivity(intent, eventTime, mCallbacks, null, null)); 252 } 253 gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED); 254 return mCallbacks; 255 } 256 257 /** 258 * Continues the existing running recents animation for a new gesture. 259 */ continueRecentsAnimation(GestureState gestureState)260 public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) { 261 ActiveGestureLog.INSTANCE.addLog(/* event= */ "continueRecentsAnimation"); 262 mCallbacks.removeListener(mLastGestureState); 263 mLastGestureState = gestureState; 264 mCallbacks.addListener(gestureState); 265 gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED 266 | STATE_RECENTS_ANIMATION_STARTED); 267 gestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); 268 return mCallbacks; 269 } 270 endLiveTile()271 public void endLiveTile() { 272 if (mLastGestureState == null) { 273 return; 274 } 275 BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface(); 276 if (activityInterface.isInLiveTileMode() 277 && activityInterface.getCreatedActivity() != null) { 278 RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel(); 279 if (recentsView != null) { 280 recentsView.switchToScreenshot(null, 281 () -> recentsView.finishRecentsAnimation(true /* toRecents */, 282 false /* shouldPip */, null)); 283 } 284 } 285 } 286 setLiveTileCleanUpHandler(Runnable cleanUpHandler)287 public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) { 288 mLiveTileCleanUpHandler = cleanUpHandler; 289 } 290 enableLiveTileRestartListener()291 public void enableLiveTileRestartListener() { 292 TaskStackChangeListeners.getInstance().registerTaskStackListener(mLiveTileRestartListener); 293 } 294 295 /** 296 * Finishes the running recents animation. 297 */ finishRunningRecentsAnimation(boolean toHome)298 public void finishRunningRecentsAnimation(boolean toHome) { 299 if (mController != null) { 300 ActiveGestureLog.INSTANCE.addLog( 301 /* event= */ "finishRunningRecentsAnimation", toHome); 302 mCallbacks.notifyAnimationCanceled(); 303 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome 304 ? mController::finishAnimationToHome 305 : mController::finishAnimationToApp); 306 cleanUpRecentsAnimation(); 307 } 308 } 309 310 /** 311 * Used to notify a listener of the current recents animation state (used if the listener was 312 * not yet added to the callbacks at the point that the listener callbacks would have been 313 * made). 314 */ notifyRecentsAnimationState( RecentsAnimationCallbacks.RecentsAnimationListener listener)315 public void notifyRecentsAnimationState( 316 RecentsAnimationCallbacks.RecentsAnimationListener listener) { 317 if (isRecentsAnimationRunning()) { 318 listener.onRecentsAnimationStart(mController, mTargets); 319 } 320 // TODO: Do we actually need to report canceled/finished? 321 } 322 323 /** 324 * @return whether there is a recents animation running. 325 */ isRecentsAnimationRunning()326 public boolean isRecentsAnimationRunning() { 327 return mController != null; 328 } 329 330 /** 331 * Cleans up the recents animation entirely. 332 */ cleanUpRecentsAnimation()333 private void cleanUpRecentsAnimation() { 334 ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation"); 335 if (mLiveTileCleanUpHandler != null) { 336 mLiveTileCleanUpHandler.run(); 337 mLiveTileCleanUpHandler = null; 338 } 339 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mLiveTileRestartListener); 340 341 // Release all the target leashes 342 if (mTargets != null) { 343 mTargets.release(); 344 } 345 346 // Clean up all listeners to ensure we don't get subsequent callbacks 347 if (mCallbacks != null) { 348 mCallbacks.removeAllListeners(); 349 } 350 351 mController = null; 352 mCallbacks = null; 353 mTargets = null; 354 mLastGestureState = null; 355 mLastAppearedTaskTarget = null; 356 } 357 358 @Nullable getCurrentCallbacks()359 public RecentsAnimationCallbacks getCurrentCallbacks() { 360 return mCallbacks; 361 } 362 dump()363 public void dump() { 364 // TODO 365 } 366 } 367