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