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