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.view.RemoteAnimationTarget.MODE_CLOSING;
19 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
20 
21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
22 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_CANCEL_RECENTS_ANIMATION;
23 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_FINISH_RECENTS_ANIMATION;
24 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_START_RECENTS_ANIMATION;
25 
26 import android.graphics.Rect;
27 import android.os.Bundle;
28 import android.util.ArraySet;
29 import android.view.RemoteAnimationTarget;
30 
31 import androidx.annotation.BinderThread;
32 import androidx.annotation.NonNull;
33 import androidx.annotation.UiThread;
34 
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.util.Preconditions;
37 import com.android.quickstep.util.ActiveGestureErrorDetector;
38 import com.android.quickstep.util.ActiveGestureLog;
39 import com.android.systemui.shared.recents.model.ThumbnailData;
40 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
41 
42 import java.io.PrintWriter;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.HashMap;
46 import java.util.Set;
47 
48 /**
49  * Wrapper around {@link com.android.systemui.shared.system.RecentsAnimationListener} which
50  * delegates callbacks to multiple listeners on the main thread
51  */
52 public class RecentsAnimationCallbacks implements
53         com.android.systemui.shared.system.RecentsAnimationListener {
54 
55     private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
56     private final SystemUiProxy mSystemUiProxy;
57     private final boolean mAllowMinimizeSplitScreen;
58 
59     // TODO(141886704): Remove these references when they are no longer needed
60     private RecentsAnimationController mController;
61 
62     private boolean mCancelled;
63 
RecentsAnimationCallbacks(SystemUiProxy systemUiProxy, boolean allowMinimizeSplitScreen)64     public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
65             boolean allowMinimizeSplitScreen) {
66         mSystemUiProxy = systemUiProxy;
67         mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
68     }
69 
70     @UiThread
addListener(RecentsAnimationListener listener)71     public void addListener(RecentsAnimationListener listener) {
72         Preconditions.assertUIThread();
73         mListeners.add(listener);
74     }
75 
76     @UiThread
removeListener(RecentsAnimationListener listener)77     public void removeListener(RecentsAnimationListener listener) {
78         Preconditions.assertUIThread();
79         mListeners.remove(listener);
80     }
81 
82     @UiThread
removeAllListeners()83     public void removeAllListeners() {
84         Preconditions.assertUIThread();
85         mListeners.clear();
86     }
87 
notifyAnimationCanceled()88     public void notifyAnimationCanceled() {
89         mCancelled = true;
90         onAnimationCanceled(new HashMap<>());
91     }
92 
93     // Called only in Q platform
94     @BinderThread
95     @Deprecated
onAnimationStart(RecentsAnimationControllerCompat controller, RemoteAnimationTarget[] appTargets, Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras)96     public final void onAnimationStart(RecentsAnimationControllerCompat controller,
97             RemoteAnimationTarget[] appTargets, Rect homeContentInsets,
98             Rect minimizedHomeBounds, Bundle extras) {
99         onAnimationStart(controller, appTargets, new RemoteAnimationTarget[0],
100                 homeContentInsets, minimizedHomeBounds, extras);
101     }
102 
103     // Called only in R+ platform
104     @BinderThread
onAnimationStart(RecentsAnimationControllerCompat animationController, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras)105     public final void onAnimationStart(RecentsAnimationControllerCompat animationController,
106             RemoteAnimationTarget[] appTargets,
107             RemoteAnimationTarget[] wallpaperTargets,
108             Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras) {
109         long appCount = Arrays.stream(appTargets)
110                 .filter(app -> app.mode == MODE_CLOSING)
111                 .count();
112         if (appCount == 0) {
113             // Edge case, if there are no closing app targets, then Launcher has nothing to handle
114             ActiveGestureLog.INSTANCE.addLog(
115                     /* event= */ "RecentsAnimationCallbacks.onAnimationStart (canceled)",
116                     /* extras= */ 0,
117                     /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
118             notifyAnimationCanceled();
119             animationController.finish(false /* toHome */, false /* sendUserLeaveHint */,
120                     null /* finishCb */);
121             return;
122         }
123 
124         mController = new RecentsAnimationController(animationController,
125                 mAllowMinimizeSplitScreen, this::onAnimationFinished);
126         if (mCancelled) {
127             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
128                     mController::finishAnimationToApp);
129         } else {
130             RemoteAnimationTarget[] nonAppTargets;
131             if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
132                 nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(appTargets);
133             } else {
134                 final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
135                 final ArrayList<RemoteAnimationTarget> nonApps = new ArrayList<>();
136                 classifyTargets(appTargets, apps, nonApps);
137                 appTargets = apps.toArray(new RemoteAnimationTarget[apps.size()]);
138                 nonAppTargets = nonApps.toArray(new RemoteAnimationTarget[nonApps.size()]);
139             }
140             if (nonAppTargets == null) {
141                 nonAppTargets = new RemoteAnimationTarget[0];
142             }
143             final RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
144                     wallpaperTargets, nonAppTargets, homeContentInsets, minimizedHomeBounds,
145                     extras);
146 
147             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
148                 ActiveGestureLog.INSTANCE.addLog(
149                         /* event= */ "RecentsAnimationCallbacks.onAnimationStart",
150                         /* extras= */ targets.apps.length,
151                         /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
152                 for (RecentsAnimationListener listener : getListeners()) {
153                     listener.onRecentsAnimationStart(mController, targets);
154                 }
155             });
156         }
157     }
158 
159     @BinderThread
160     @Override
onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)161     public final void onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
162         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
163             ActiveGestureLog.INSTANCE.addLog(
164                     /* event= */ "RecentsAnimationCallbacks.onAnimationCanceled",
165                     /* gestureEvent= */ ON_CANCEL_RECENTS_ANIMATION);
166             for (RecentsAnimationListener listener : getListeners()) {
167                 listener.onRecentsAnimationCanceled(thumbnailDatas);
168             }
169         });
170     }
171 
172     @BinderThread
173     @Override
onTasksAppeared(RemoteAnimationTarget[] apps)174     public void onTasksAppeared(RemoteAnimationTarget[] apps) {
175         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
176             ActiveGestureLog.INSTANCE.addLog("RecentsAnimationCallbacks.onTasksAppeared",
177                     ActiveGestureErrorDetector.GestureEvent.TASK_APPEARED);
178             for (RecentsAnimationListener listener : getListeners()) {
179                 listener.onTasksAppeared(apps);
180             }
181         });
182     }
183 
184     @BinderThread
185     @Override
onSwitchToScreenshot(Runnable onFinished)186     public boolean onSwitchToScreenshot(Runnable onFinished) {
187         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
188             for (RecentsAnimationListener listener : getListeners()) {
189                 if (listener.onSwitchToScreenshot(onFinished)) return;
190             }
191             onFinished.run();
192         });
193         return true;
194     }
195 
onAnimationFinished(RecentsAnimationController controller)196     private final void onAnimationFinished(RecentsAnimationController controller) {
197         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
198             ActiveGestureLog.INSTANCE.addLog(
199                     /* event= */ "RecentsAnimationCallbacks.onAnimationFinished",
200                     ON_FINISH_RECENTS_ANIMATION);
201             for (RecentsAnimationListener listener : getListeners()) {
202                 listener.onRecentsAnimationFinished(controller);
203             }
204         });
205     }
206 
getListeners()207     private RecentsAnimationListener[] getListeners() {
208         return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
209     }
210 
classifyTargets(RemoteAnimationTarget[] appTargets, ArrayList<RemoteAnimationTarget> apps, ArrayList<RemoteAnimationTarget> nonApps)211     private void classifyTargets(RemoteAnimationTarget[] appTargets,
212             ArrayList<RemoteAnimationTarget> apps, ArrayList<RemoteAnimationTarget> nonApps) {
213         for (int i = 0; i < appTargets.length; i++) {
214             RemoteAnimationTarget target = appTargets[i];
215             if (target.windowType == TYPE_DOCK_DIVIDER) {
216                 nonApps.add(target);
217             } else {
218                 apps.add(target);
219             }
220         }
221     }
222 
dump(String prefix, PrintWriter pw)223     public void dump(String prefix, PrintWriter pw) {
224         pw.println(prefix + "RecentsAnimationCallbacks:");
225 
226         pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
227         pw.println(prefix + "\tmCancelled=" + mCancelled);
228     }
229 
230     /**
231      * Listener for the recents animation callbacks.
232      */
233     public interface RecentsAnimationListener {
onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)234         default void onRecentsAnimationStart(RecentsAnimationController controller,
235                 RecentsAnimationTargets targets) {}
236 
237         /**
238          * Callback from the system when the recents animation is canceled. {@param thumbnailData}
239          * is passed back for rendering screenshot to replace live tile.
240          */
onRecentsAnimationCanceled( @onNull HashMap<Integer, ThumbnailData> thumbnailDatas)241         default void onRecentsAnimationCanceled(
242                 @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {}
243 
244         /**
245          * Callback made whenever the recents animation is finished.
246          */
onRecentsAnimationFinished(@onNull RecentsAnimationController controller)247         default void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) {}
248 
249         /**
250          * Callback made when a task started from the recents is ready for an app transition.
251          */
onTasksAppeared(@onNull RemoteAnimationTarget[] appearedTaskTarget)252         default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {}
253 
254         /**
255          * @return whether this will call onFinished or not (onFinished should only be called once).
256          */
onSwitchToScreenshot(Runnable onFinished)257         default boolean onSwitchToScreenshot(Runnable onFinished) {
258             return false;
259         }
260     }
261 }
262