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