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