1 /*
2  * Copyright (C) 2018 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.util.Executors.MAIN_EXECUTOR;
19 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
20 
21 import android.annotation.TargetApi;
22 import android.content.Intent;
23 import android.graphics.PointF;
24 import android.os.Build;
25 import android.os.SystemClock;
26 import android.os.Trace;
27 
28 import androidx.annotation.BinderThread;
29 import androidx.annotation.Nullable;
30 import androidx.annotation.UiThread;
31 
32 import com.android.launcher3.statemanager.StatefulActivity;
33 import com.android.launcher3.util.RunnableList;
34 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
35 import com.android.quickstep.util.LauncherSplitScreenListener;
36 import com.android.quickstep.views.RecentsView;
37 import com.android.quickstep.views.TaskView;
38 import com.android.systemui.shared.recents.model.ThumbnailData;
39 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
40 
41 import java.io.PrintWriter;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 
45 /**
46  * Helper class to handle various atomic commands for switching between Overview.
47  */
48 @TargetApi(Build.VERSION_CODES.P)
49 public class OverviewCommandHelper {
50 
51     public static final int TYPE_SHOW = 1;
52     public static final int TYPE_SHOW_NEXT_FOCUS = 2;
53     public static final int TYPE_HIDE = 3;
54     public static final int TYPE_TOGGLE = 4;
55     public static final int TYPE_HOME = 5;
56 
57     private static final String TRANSITION_NAME = "Transition:toOverview";
58 
59     private final TouchInteractionService mService;
60     private final OverviewComponentObserver mOverviewComponentObserver;
61     private final TaskAnimationManager mTaskAnimationManager;
62     private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
63 
OverviewCommandHelper(TouchInteractionService service, OverviewComponentObserver observer, TaskAnimationManager taskAnimationManager)64     public OverviewCommandHelper(TouchInteractionService service,
65             OverviewComponentObserver observer,
66             TaskAnimationManager taskAnimationManager) {
67         mService = service;
68         mOverviewComponentObserver = observer;
69         mTaskAnimationManager = taskAnimationManager;
70     }
71 
72     /**
73      * Called when the command finishes execution.
74      */
scheduleNextTask(CommandInfo command)75     private void scheduleNextTask(CommandInfo command) {
76         if (!mPendingCommands.isEmpty() && mPendingCommands.get(0) == command) {
77             mPendingCommands.remove(0);
78             executeNext();
79         }
80     }
81 
82     /**
83      * Executes the next command from the queue. If the command finishes immediately (returns true),
84      * it continues to execute the next command, until the queue is empty of a command defer's its
85      * completion (returns false).
86      */
87     @UiThread
executeNext()88     private void executeNext() {
89         if (mPendingCommands.isEmpty()) {
90             return;
91         }
92         CommandInfo cmd = mPendingCommands.get(0);
93         if (executeCommand(cmd)) {
94             scheduleNextTask(cmd);
95         }
96     }
97 
98     @UiThread
addCommand(CommandInfo cmd)99     private void addCommand(CommandInfo cmd) {
100         boolean wasEmpty = mPendingCommands.isEmpty();
101         mPendingCommands.add(cmd);
102         if (wasEmpty) {
103             executeNext();
104         }
105     }
106 
107     /**
108      * Adds a command to be executed next, after all pending tasks are completed
109      */
110     @BinderThread
addCommand(int type)111     public void addCommand(int type) {
112         CommandInfo cmd = new CommandInfo(type);
113         MAIN_EXECUTOR.execute(() -> addCommand(cmd));
114     }
115 
116     @UiThread
clearPendingCommands()117     public void clearPendingCommands() {
118         mPendingCommands.clear();
119     }
120 
121     @Nullable
getNextTask(RecentsView view)122     private TaskView getNextTask(RecentsView view) {
123         final TaskView runningTaskView = view.getRunningTaskView();
124 
125         if (runningTaskView == null) {
126             return view.getTaskViewAt(0);
127         } else {
128             final TaskView nextTask = view.getNextTaskView();
129             return nextTask != null ? nextTask : runningTaskView;
130         }
131     }
132 
launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd)133     private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
134         RunnableList callbackList = null;
135         if (taskView != null) {
136             taskView.setEndQuickswitchCuj(true);
137             callbackList = taskView.launchTaskAnimated();
138         }
139 
140         if (callbackList != null) {
141             callbackList.add(() -> scheduleNextTask(cmd));
142             return false;
143         } else {
144             recents.startHome();
145             return true;
146         }
147     }
148 
149     /**
150      * Executes the task and returns true if next task can be executed. If false, then the next
151      * task is deferred until {@link #scheduleNextTask} is called
152      */
executeCommand(CommandInfo cmd)153     private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
154         BaseActivityInterface<?, T> activityInterface =
155                 mOverviewComponentObserver.getActivityInterface();
156         RecentsView recents = activityInterface.getVisibleRecentsView();
157         if (recents == null) {
158             if (cmd.type == TYPE_HIDE) {
159                 // already hidden
160                 return true;
161             }
162             if (cmd.type == TYPE_HOME) {
163                 mService.startActivity(mOverviewComponentObserver.getHomeIntent());
164                 LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
165                 return true;
166             }
167         } else {
168             switch (cmd.type) {
169                 case TYPE_SHOW:
170                     // already visible
171                     return true;
172                 case TYPE_HIDE: {
173                     int currentPage = recents.getNextPage();
174                     TaskView tv = (currentPage >= 0 && currentPage < recents.getTaskViewCount())
175                             ? (TaskView) recents.getPageAt(currentPage)
176                             : null;
177                     return launchTask(recents, tv, cmd);
178                 }
179                 case TYPE_TOGGLE:
180                     return launchTask(recents, getNextTask(recents), cmd);
181                 case TYPE_HOME:
182                     recents.startHome();
183                     LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
184                     return true;
185             }
186         }
187 
188         if (activityInterface.switchToRecentsIfVisible(() -> scheduleNextTask(cmd))) {
189             // If successfully switched, wait until animation finishes
190             return false;
191         }
192 
193         final T activity = activityInterface.getCreatedActivity();
194         if (activity != null) {
195             InteractionJankMonitorWrapper.begin(
196                     activity.getRootView(),
197                     InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
198         }
199 
200         GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE);
201         gestureState.setHandlingAtomicEvent(true);
202         AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
203                 .newHandler(gestureState, cmd.createTime);
204         interactionHandler.setGestureEndCallback(
205                 () -> onTransitionComplete(cmd, interactionHandler));
206         interactionHandler.initWhenReady();
207 
208         RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
209             @Override
210             public void onRecentsAnimationStart(RecentsAnimationController controller,
211                     RecentsAnimationTargets targets) {
212                 interactionHandler.onGestureEnded(0, new PointF(), new PointF());
213                 cmd.removeListener(this);
214             }
215 
216             @Override
217             public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
218                 interactionHandler.onGestureCancelled();
219                 cmd.removeListener(this);
220 
221                 RecentsView createdRecents =
222                         activityInterface.getCreatedActivity().getOverviewPanel();
223                 if (createdRecents != null) {
224                     createdRecents.onRecentsAnimationComplete();
225                 }
226             }
227         };
228 
229         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
230             cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
231             cmd.mActiveCallbacks.addListener(interactionHandler);
232             mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
233             interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
234 
235             cmd.mActiveCallbacks.addListener(recentAnimListener);
236             mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
237         } else {
238             Intent intent = new Intent(interactionHandler.getLaunchIntent());
239             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
240             cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
241                     gestureState, intent, interactionHandler);
242             interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
243             cmd.mActiveCallbacks.addListener(recentAnimListener);
244         }
245 
246         Trace.beginAsyncSection(TRANSITION_NAME, 0);
247         return false;
248     }
249 
onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler)250     private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
251         cmd.removeListener(handler);
252         Trace.endAsyncSection(TRANSITION_NAME, 0);
253 
254         if (cmd.type == TYPE_SHOW_NEXT_FOCUS) {
255             RecentsView rv =
256                     mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
257             if (rv != null) {
258                 // Ensure that recents view has focus so that it receives the followup key inputs
259                 TaskView taskView = rv.getNextTaskView();
260                 if (taskView == null) {
261                     taskView = rv.getTaskViewAt(0);
262                     if (taskView != null) {
263                         taskView.requestFocus();
264                     } else {
265                         rv.requestFocus();
266                     }
267                 } else {
268                     taskView.requestFocus();
269                 }
270             }
271         }
272         scheduleNextTask(cmd);
273     }
274 
dump(PrintWriter pw)275     public void dump(PrintWriter pw) {
276         pw.println("OverviewCommandHelper:");
277         pw.println("  mPendingCommands=" + mPendingCommands.size());
278         if (!mPendingCommands.isEmpty()) {
279             pw.println("    pendingCommandType=" + mPendingCommands.get(0).type);
280         }
281     }
282 
283     private static class CommandInfo {
284         public final long createTime = SystemClock.elapsedRealtime();
285         public final int type;
286         RecentsAnimationCallbacks mActiveCallbacks;
287 
CommandInfo(int type)288         CommandInfo(int type) {
289             this.type = type;
290         }
291 
removeListener(RecentsAnimationListener listener)292         void removeListener(RecentsAnimationListener listener) {
293             if (mActiveCallbacks != null) {
294                 mActiveCallbacks.removeListener(listener);
295             }
296         }
297     }
298 }
299