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