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