• 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 android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
19 
20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
21 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
22 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
23 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
24 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
25 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
26 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
27 
28 import android.app.ActivityManager;
29 import android.app.ActivityOptions;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.os.SystemProperties;
33 import android.util.Log;
34 import android.view.RemoteAnimationTarget;
35 
36 import androidx.annotation.Nullable;
37 import androidx.annotation.UiThread;
38 
39 import com.android.launcher3.Utilities;
40 import com.android.launcher3.config.FeatureFlags;
41 import com.android.launcher3.testing.shared.TestProtocol;
42 import com.android.launcher3.util.DisplayController;
43 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
44 import com.android.quickstep.util.ActiveGestureLog;
45 import com.android.quickstep.views.DesktopTaskView;
46 import com.android.quickstep.views.RecentsView;
47 import com.android.systemui.shared.recents.model.ThumbnailData;
48 import com.android.systemui.shared.system.ActivityManagerWrapper;
49 import com.android.systemui.shared.system.TaskStackChangeListener;
50 import com.android.systemui.shared.system.TaskStackChangeListeners;
51 
52 import java.util.HashMap;
53 
54 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
55     public static final boolean ENABLE_SHELL_TRANSITIONS =
56             SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
57     public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
58             && SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
59 
60     private RecentsAnimationController mController;
61     private RecentsAnimationCallbacks mCallbacks;
62     private RecentsAnimationTargets mTargets;
63     // Temporary until we can hook into gesture state events
64     private GestureState mLastGestureState;
65     private RemoteAnimationTarget[] mLastAppearedTaskTargets;
66     private Runnable mLiveTileCleanUpHandler;
67     private Context mCtx;
68 
69     private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
70         @Override
71         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
72                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
73             if (mLastGestureState == null) {
74                 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
75                         mLiveTileRestartListener);
76                 return;
77             }
78             BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
79             if (activityInterface.isInLiveTileMode()
80                     && activityInterface.getCreatedActivity() != null) {
81                 RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
82                 if (recentsView != null) {
83                     recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId);
84                     TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
85                             mLiveTileRestartListener);
86                 }
87             }
88         }
89     };
90 
TaskAnimationManager(Context ctx)91     TaskAnimationManager(Context ctx) {
92         mCtx = ctx;
93     }
94     /**
95      * Preloads the recents animation.
96      */
preloadRecentsAnimation(Intent intent)97     public void preloadRecentsAnimation(Intent intent) {
98         // Pass null animation handler to indicate this start is for preloading
99         UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
100                 .startRecentsActivity(intent, 0, null, null, null));
101     }
102 
103     /**
104      * Starts a new recents animation for the activity with the given {@param intent}.
105      */
106     @UiThread
startRecentsAnimation(GestureState gestureState, Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener)107     public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
108             Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
109         ActiveGestureLog.INSTANCE.addLog(
110                 /* event= */ "startRecentsAnimation",
111                 /* gestureEvent= */ START_RECENTS_ANIMATION);
112         // Notify if recents animation is still running
113         if (mController != null) {
114             String msg = "New recents animation started before old animation completed";
115             if (FeatureFlags.IS_STUDIO_BUILD) {
116                 throw new IllegalArgumentException(msg);
117             } else {
118                 Log.e("TaskAnimationManager", msg, new Exception());
119             }
120         }
121         // But force-finish it anyways
122         finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */);
123 
124         if (mCallbacks != null) {
125             // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
126             // before the previous one got onRecentsAnimationStart(). In that case, cleanup the
127             // previous animation so it doesn't mess up/listen to state changes in this animation.
128             cleanUpRecentsAnimation(mCallbacks);
129         }
130 
131         final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
132         mLastGestureState = gestureState;
133         RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
134                 SystemUiProxy.INSTANCE.get(mCtx), activityInterface.allowMinimizeSplitScreen());
135         mCallbacks = newCallbacks;
136         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
137             @Override
138             public void onRecentsAnimationStart(RecentsAnimationController controller,
139                     RecentsAnimationTargets targets) {
140                 if (mCallbacks == null) {
141                     // It's possible for the recents animation to have finished and be cleaned up
142                     // by the time we process the start callback, and in that case, just we can skip
143                     // handling this call entirely
144                     return;
145                 }
146                 mController = controller;
147                 mTargets = targets;
148                 // TODO(b/236226779): We can probably get away w/ setting mLastAppearedTaskTargets
149                 //  to all appeared targets directly vs just looking at running ones
150                 int[] runningTaskIds = mLastGestureState.getRunningTaskIds(targets.apps.length > 1);
151                 mLastAppearedTaskTargets = new RemoteAnimationTarget[runningTaskIds.length];
152                 for (int i = 0; i < runningTaskIds.length; i++) {
153                     RemoteAnimationTarget task = mTargets.findTask(runningTaskIds[i]);
154                     mLastAppearedTaskTargets[i] = task;
155                 }
156                 mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
157             }
158 
159             @Override
160             public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
161                 cleanUpRecentsAnimation(newCallbacks);
162             }
163 
164             @Override
165             public void onRecentsAnimationFinished(RecentsAnimationController controller) {
166                 cleanUpRecentsAnimation(newCallbacks);
167             }
168 
169             @Override
170             public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
171                 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
172                 BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
173 
174                 for (RemoteAnimationTarget compat : appearedTaskTargets) {
175                     if (compat.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME
176                             && activityInterface.getCreatedActivity() instanceof RecentsActivity
177                             && DisplayController.getNavigationMode(mCtx) != NO_BUTTON) {
178                         // The only time we get onTasksAppeared() in button navigation with a
179                         // 3p launcher is if the user goes to overview first, and in this case we
180                         // can immediately finish the transition
181                         RecentsView recentsView =
182                                 activityInterface.getCreatedActivity().getOverviewPanel();
183                         if (recentsView != null) {
184                             Log.d(TestProtocol.INCORRECT_HOME_STATE,
185                                     "finish recents animation on "
186                                             + compat.taskInfo.description);
187                             recentsView.finishRecentsAnimation(true, null);
188                         }
189                         return;
190                     }
191                 }
192 
193                 RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.get(mCtx)
194                         .onStartingSplitLegacy(appearedTaskTargets);
195                 if (nonAppTargets == null) {
196                     nonAppTargets = new RemoteAnimationTarget[0];
197                 }
198                 if ((activityInterface.isInLiveTileMode()
199                             || mLastGestureState.getEndTarget() == RECENTS)
200                         && activityInterface.getCreatedActivity() != null) {
201                     RecentsView recentsView =
202                             activityInterface.getCreatedActivity().getOverviewPanel();
203                     if (recentsView != null) {
204                         ActiveGestureLog.INSTANCE.addLog("Launching side task id="
205                                 + appearedTaskTarget.taskId);
206                         recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
207                                 appearedTaskTargets,
208                                 new RemoteAnimationTarget[0] /* wallpaper */,
209                                 nonAppTargets /* nonApps */);
210                         return;
211                     } else {
212                         ActiveGestureLog.INSTANCE.addLog("Unable to launch side task (no recents)");
213                     }
214                 } else if (nonAppTargets.length > 0) {
215                     TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets /* nonApps */,
216                             true /*shown*/, null /* animatorHandler */);
217                 }
218                 if (mController != null) {
219                     if (mLastAppearedTaskTargets != null) {
220                         for (RemoteAnimationTarget lastTarget : mLastAppearedTaskTargets) {
221                             for (RemoteAnimationTarget appearedTarget : appearedTaskTargets) {
222                                 if (lastTarget != null &&
223                                         appearedTarget.taskId != lastTarget.taskId) {
224                                     mController.removeTaskTarget(lastTarget.taskId);
225                                 }
226                             }
227                         }
228                     }
229                     mLastAppearedTaskTargets = appearedTaskTargets;
230                     mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
231                 }
232             }
233 
234             @Override
235             public boolean onSwitchToScreenshot(Runnable onFinished) {
236                 if (!activityInterface.isInLiveTileMode()
237                         || activityInterface.getCreatedActivity() == null) {
238                     // No need to switch since tile is already a screenshot.
239                     onFinished.run();
240                 } else {
241                     final RecentsView recentsView =
242                             activityInterface.getCreatedActivity().getOverviewPanel();
243                     if (recentsView != null) {
244                         recentsView.switchToScreenshot(onFinished);
245                     } else {
246                         onFinished.run();
247                     }
248                 }
249                 return true;
250             }
251         });
252         final long eventTime = gestureState.getSwipeUpStartTimeMs();
253         mCallbacks.addListener(gestureState);
254         mCallbacks.addListener(listener);
255 
256         if (ENABLE_SHELL_TRANSITIONS) {
257             final ActivityOptions options = ActivityOptions.makeBasic();
258             // Allowing to pause Home if Home is top activity and Recents is not Home. So when user
259             // start home when recents animation is playing, the home activity can be resumed again
260             // to let the transition controller collect Home activity.
261             CachedTaskInfo cti = gestureState.getRunningTask();
262             boolean homeIsOnTop = cti != null && cti.isHomeTask();
263             if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
264                 if (cti != null && cti.isFreeformTask()) {
265                     // No transient launch when desktop task is on top
266                     homeIsOnTop = true;
267                 }
268             }
269             if (activityInterface.allowAllAppsFromOverview()) {
270                 homeIsOnTop = true;
271             }
272             if (!homeIsOnTop) {
273                 options.setTransientLaunch();
274             }
275             options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
276             SystemUiProxy.INSTANCE.getNoCreate().startRecentsActivity(intent, options, mCallbacks);
277         } else {
278             UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
279                     .startRecentsActivity(intent, eventTime, mCallbacks, null, null));
280         }
281         gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
282         return mCallbacks;
283     }
284 
285     /**
286      * Continues the existing running recents animation for a new gesture.
287      */
continueRecentsAnimation(GestureState gestureState)288     public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) {
289         ActiveGestureLog.INSTANCE.addLog(/* event= */ "continueRecentsAnimation");
290         mCallbacks.removeListener(mLastGestureState);
291         mLastGestureState = gestureState;
292         mCallbacks.addListener(gestureState);
293         gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED
294                 | STATE_RECENTS_ANIMATION_STARTED);
295         gestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
296         return mCallbacks;
297     }
298 
endLiveTile()299     public void endLiveTile() {
300         if (mLastGestureState == null) {
301             return;
302         }
303         BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
304         if (activityInterface.isInLiveTileMode()
305                 && activityInterface.getCreatedActivity() != null) {
306             RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
307             if (recentsView != null) {
308                 recentsView.switchToScreenshot(null,
309                         () -> recentsView.finishRecentsAnimation(true /* toRecents */,
310                                 false /* shouldPip */, null));
311             }
312         }
313     }
314 
setLiveTileCleanUpHandler(Runnable cleanUpHandler)315     public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
316         mLiveTileCleanUpHandler = cleanUpHandler;
317     }
318 
enableLiveTileRestartListener()319     public void enableLiveTileRestartListener() {
320         TaskStackChangeListeners.getInstance().registerTaskStackListener(mLiveTileRestartListener);
321     }
322 
323     /**
324      * Finishes the running recents animation.
325      */
finishRunningRecentsAnimation(boolean toHome)326     public void finishRunningRecentsAnimation(boolean toHome) {
327         finishRunningRecentsAnimation(toHome, false /* forceFinish */);
328     }
329 
330     /**
331      * Finishes the running recents animation.
332      * @param forceFinish will synchronously finish the controller
333      */
finishRunningRecentsAnimation(boolean toHome, boolean forceFinish)334     public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
335         if (mController != null) {
336             ActiveGestureLog.INSTANCE.addLog(
337                     /* event= */ "finishRunningRecentsAnimation", toHome);
338             if (forceFinish) {
339                 mController.finishController(toHome, null, false /* sendUserLeaveHint */,
340                         true /* forceFinish */);
341             } else {
342                 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
343                         ? mController::finishAnimationToHome
344                         : mController::finishAnimationToApp);
345             }
346         }
347     }
348 
349     /**
350      * Used to notify a listener of the current recents animation state (used if the listener was
351      * not yet added to the callbacks at the point that the listener callbacks would have been
352      * made).
353      */
notifyRecentsAnimationState( RecentsAnimationCallbacks.RecentsAnimationListener listener)354     public void notifyRecentsAnimationState(
355             RecentsAnimationCallbacks.RecentsAnimationListener listener) {
356         if (isRecentsAnimationRunning()) {
357             listener.onRecentsAnimationStart(mController, mTargets);
358         }
359         // TODO: Do we actually need to report canceled/finished?
360     }
361 
362     /**
363      * @return whether there is a recents animation running.
364      */
isRecentsAnimationRunning()365     public boolean isRecentsAnimationRunning() {
366         return mController != null;
367     }
368 
369     /**
370      * Cleans up the recents animation entirely.
371      */
cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks)372     private void cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks) {
373         if (mCallbacks != targetCallbacks) {
374             ActiveGestureLog.INSTANCE.addLog(
375                     /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
376             return;
377         }
378         ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
379         if (mLiveTileCleanUpHandler != null) {
380             mLiveTileCleanUpHandler.run();
381             mLiveTileCleanUpHandler = null;
382         }
383         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mLiveTileRestartListener);
384 
385         // Release all the target leashes
386         if (mTargets != null) {
387             mTargets.release();
388         }
389 
390         // Clean up all listeners to ensure we don't get subsequent callbacks
391         if (mCallbacks != null) {
392             mCallbacks.removeAllListeners();
393         }
394 
395         mController = null;
396         mCallbacks = null;
397         mTargets = null;
398         mLastGestureState = null;
399         mLastAppearedTaskTargets = null;
400     }
401 
402     @Nullable
getCurrentCallbacks()403     public RecentsAnimationCallbacks getCurrentCallbacks() {
404         return mCallbacks;
405     }
406 
dump()407     public void dump() {
408         // TODO
409     }
410 }
411