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