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