• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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