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.util.Executors.MAIN_EXECUTOR; 19 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 20 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED; 21 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED; 22 23 import android.content.Intent; 24 import android.util.Log; 25 26 import androidx.annotation.UiThread; 27 28 import com.android.launcher3.Utilities; 29 import com.android.launcher3.config.FeatureFlags; 30 import com.android.systemui.shared.recents.model.ThumbnailData; 31 import com.android.systemui.shared.system.ActivityManagerWrapper; 32 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 33 34 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener { 35 36 private RecentsAnimationController mController; 37 private RecentsAnimationCallbacks mCallbacks; 38 private RecentsAnimationTargets mTargets; 39 // Temporary until we can hook into gesture state events 40 private GestureState mLastGestureState; 41 private RemoteAnimationTargetCompat mLastAppearedTaskTarget; 42 43 /** 44 * Preloads the recents animation. 45 */ preloadRecentsAnimation(Intent intent)46 public void preloadRecentsAnimation(Intent intent) { 47 // Pass null animation handler to indicate this start is for preloading 48 UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() 49 .startRecentsActivity(intent, null, null, null, null)); 50 } 51 52 /** 53 * Starts a new recents animation for the activity with the given {@param intent}. 54 */ 55 @UiThread startRecentsAnimation(GestureState gestureState, Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener)56 public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState, 57 Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) { 58 // Notify if recents animation is still running 59 if (mController != null) { 60 String msg = "New recents animation started before old animation completed"; 61 if (FeatureFlags.IS_STUDIO_BUILD) { 62 throw new IllegalArgumentException(msg); 63 } else { 64 Log.e("TaskAnimationManager", msg, new Exception()); 65 } 66 } 67 // But force-finish it anyways 68 finishRunningRecentsAnimation(false /* toHome */); 69 70 final BaseActivityInterface activityInterface = gestureState.getActivityInterface(); 71 mLastGestureState = gestureState; 72 mCallbacks = new RecentsAnimationCallbacks(activityInterface.allowMinimizeSplitScreen()); 73 mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() { 74 @Override 75 public void onRecentsAnimationStart(RecentsAnimationController controller, 76 RecentsAnimationTargets targets) { 77 if (mCallbacks == null) { 78 // It's possible for the recents animation to have finished and be cleaned up 79 // by the time we process the start callback, and in that case, just we can skip 80 // handling this call entirely 81 return; 82 } 83 mController = controller; 84 mTargets = targets; 85 mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId()); 86 mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); 87 } 88 89 @Override 90 public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { 91 if (thumbnailData != null) { 92 // If a screenshot is provided, switch to the screenshot before cleaning up 93 activityInterface.switchRunningTaskViewToScreenshot(thumbnailData, 94 () -> cleanUpRecentsAnimation(thumbnailData)); 95 } else { 96 cleanUpRecentsAnimation(null /* canceledThumbnail */); 97 } 98 } 99 100 @Override 101 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 102 cleanUpRecentsAnimation(null /* canceledThumbnail */); 103 } 104 105 @Override 106 public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) { 107 if (mController != null) { 108 if (mLastAppearedTaskTarget == null 109 || appearedTaskTarget.taskId != mLastAppearedTaskTarget.taskId) { 110 if (mLastAppearedTaskTarget != null) { 111 mController.removeTaskTarget(mLastAppearedTaskTarget); 112 } 113 mLastAppearedTaskTarget = appearedTaskTarget; 114 mLastGestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); 115 } 116 } 117 } 118 }); 119 mCallbacks.addListener(gestureState); 120 mCallbacks.addListener(listener); 121 UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() 122 .startRecentsActivity(intent, null, mCallbacks, null, null)); 123 gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED); 124 return mCallbacks; 125 } 126 127 /** 128 * Continues the existing running recents animation for a new gesture. 129 */ continueRecentsAnimation(GestureState gestureState)130 public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) { 131 mCallbacks.removeListener(mLastGestureState); 132 mLastGestureState = gestureState; 133 mCallbacks.addListener(gestureState); 134 gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED 135 | STATE_RECENTS_ANIMATION_STARTED); 136 gestureState.updateLastAppearedTaskTarget(mLastAppearedTaskTarget); 137 return mCallbacks; 138 } 139 140 /** 141 * Finishes the running recents animation. 142 */ finishRunningRecentsAnimation(boolean toHome)143 public void finishRunningRecentsAnimation(boolean toHome) { 144 if (mController != null) { 145 mCallbacks.notifyAnimationCanceled(); 146 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome 147 ? mController::finishAnimationToHome 148 : mController::finishAnimationToApp); 149 cleanUpRecentsAnimation(null /* canceledThumbnail */); 150 } 151 } 152 153 /** 154 * Used to notify a listener of the current recents animation state (used if the listener was 155 * not yet added to the callbacks at the point that the listener callbacks would have been 156 * made). 157 */ notifyRecentsAnimationState( RecentsAnimationCallbacks.RecentsAnimationListener listener)158 public void notifyRecentsAnimationState( 159 RecentsAnimationCallbacks.RecentsAnimationListener listener) { 160 if (isRecentsAnimationRunning()) { 161 listener.onRecentsAnimationStart(mController, mTargets); 162 } 163 // TODO: Do we actually need to report canceled/finished? 164 } 165 166 /** 167 * @return whether there is a recents animation running. 168 */ isRecentsAnimationRunning()169 public boolean isRecentsAnimationRunning() { 170 return mController != null; 171 } 172 173 /** 174 * Cleans up the recents animation entirely. 175 */ cleanUpRecentsAnimation(ThumbnailData canceledThumbnail)176 private void cleanUpRecentsAnimation(ThumbnailData canceledThumbnail) { 177 // Clean up the screenshot if necessary 178 if (mController != null && canceledThumbnail != null) { 179 mController.cleanupScreenshot(); 180 } 181 182 // Release all the target leashes 183 if (mTargets != null) { 184 mTargets.release(); 185 } 186 187 // Clean up all listeners to ensure we don't get subsequent callbacks 188 if (mCallbacks != null) { 189 mCallbacks.removeAllListeners(); 190 } 191 192 mController = null; 193 mCallbacks = null; 194 mTargets = null; 195 mLastGestureState = null; 196 mLastAppearedTaskTarget = null; 197 } 198 dump()199 public void dump() { 200 // TODO 201 } 202 } 203