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.app.ActivityTaskManager.INVALID_TASK_ID; 19 20 import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe; 21 import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe; 22 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS; 23 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; 24 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; 25 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW; 26 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; 27 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET; 28 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_ALL_APPS; 29 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME; 30 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK; 31 32 import android.annotation.Nullable; 33 import android.annotation.TargetApi; 34 import android.content.Intent; 35 import android.os.Build; 36 import android.view.MotionEvent; 37 import android.view.RemoteAnimationTarget; 38 39 import com.android.launcher3.statemanager.BaseState; 40 import com.android.launcher3.statemanager.StatefulActivity; 41 import com.android.quickstep.TopTaskTracker.CachedTaskInfo; 42 import com.android.quickstep.util.ActiveGestureErrorDetector; 43 import com.android.quickstep.util.ActiveGestureLog; 44 import com.android.systemui.shared.recents.model.ThumbnailData; 45 46 import java.io.PrintWriter; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.HashMap; 50 import java.util.HashSet; 51 import java.util.List; 52 import java.util.Set; 53 import java.util.function.Predicate; 54 55 /** 56 * Manages the state for an active system gesture, listens for events from the system and Launcher, 57 * and fires events when the states change. 58 */ 59 @TargetApi(Build.VERSION_CODES.R) 60 public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener { 61 62 final Predicate<RemoteAnimationTarget> mLastStartedTaskIdPredicate = new Predicate<>() { 63 @Override 64 public boolean test(RemoteAnimationTarget targetCompat) { 65 for (int taskId : mLastStartedTaskId) { 66 if (targetCompat.taskId == taskId) { 67 return true; 68 } 69 } 70 return false; 71 } 72 }; 73 74 /** 75 * Defines the end targets of a gesture and the associated state. 76 */ 77 public enum GestureEndTarget { 78 HOME(true, LAUNCHER_STATE_HOME, false), 79 80 RECENTS(true, LAUNCHER_STATE_OVERVIEW, true), 81 82 NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true), 83 84 LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true), 85 86 ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false); 87 GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow)88 GestureEndTarget(boolean isLauncher, int containerType, 89 boolean recentsAttachedToAppWindow) { 90 this.isLauncher = isLauncher; 91 this.containerType = containerType; 92 this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; 93 } 94 95 /** Whether the target is in the launcher activity. Implicitly, if the end target is going 96 to Launcher, then we can not interrupt the animation to start another gesture. */ 97 public final boolean isLauncher; 98 /** Used to log where the user ended up after the gesture ends */ 99 public final int containerType; 100 /** Whether RecentsView should be attached to the window as we animate to this target */ 101 public final boolean recentsAttachedToAppWindow; 102 } 103 104 private static final String TAG = "GestureState"; 105 106 private static final List<String> STATE_NAMES = new ArrayList<>(); 107 public static final GestureState DEFAULT_STATE = new GestureState(); 108 109 private static int FLAG_COUNT = 0; getNextStateFlag(String name)110 private static int getNextStateFlag(String name) { 111 if (DEBUG_STATES) { 112 STATE_NAMES.add(name); 113 } 114 int index = 1 << FLAG_COUNT; 115 FLAG_COUNT++; 116 return index; 117 } 118 119 // Called when the end target as been set 120 public static final int STATE_END_TARGET_SET = 121 getNextStateFlag("STATE_END_TARGET_SET"); 122 123 // Called when the end target animation has finished 124 public static final int STATE_END_TARGET_ANIMATION_FINISHED = 125 getNextStateFlag("STATE_END_TARGET_ANIMATION_FINISHED"); 126 127 // Called when the recents animation has been requested to start 128 public static final int STATE_RECENTS_ANIMATION_INITIALIZED = 129 getNextStateFlag("STATE_RECENTS_ANIMATION_INITIALIZED"); 130 131 // Called when the recents animation is started and the TaskAnimationManager has been updated 132 // with the controller and targets 133 public static final int STATE_RECENTS_ANIMATION_STARTED = 134 getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED"); 135 136 // Called when the recents animation is canceled 137 public static final int STATE_RECENTS_ANIMATION_CANCELED = 138 getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED"); 139 140 // Called when the recents animation finishes 141 public static final int STATE_RECENTS_ANIMATION_FINISHED = 142 getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED"); 143 144 // Always called when the recents animation ends (regardless of cancel or finish) 145 public static final int STATE_RECENTS_ANIMATION_ENDED = 146 getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED"); 147 148 // Called when RecentsView stops scrolling and settles on a TaskView. 149 public static final int STATE_RECENTS_SCROLLING_FINISHED = 150 getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED"); 151 152 // Needed to interact with the current activity 153 private final Intent mHomeIntent; 154 private final Intent mOverviewIntent; 155 private final BaseActivityInterface mActivityInterface; 156 private final MultiStateCallback mStateCallback; 157 private final int mGestureId; 158 159 public enum TrackpadGestureType { 160 NONE, 161 THREE_FINGER, 162 FOUR_FINGER; 163 getTrackpadGestureType(MotionEvent event)164 public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) { 165 if (isTrackpadThreeFingerSwipe(event)) { 166 return TrackpadGestureType.THREE_FINGER; 167 } 168 if (isTrackpadFourFingerSwipe(event)) { 169 return TrackpadGestureType.FOUR_FINGER; 170 } 171 172 return TrackpadGestureType.NONE; 173 } 174 } 175 176 private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE; 177 private CachedTaskInfo mRunningTask; 178 private GestureEndTarget mEndTarget; 179 private RemoteAnimationTarget[] mLastAppearedTaskTargets; 180 private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>(); 181 private int[] mLastStartedTaskId = new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 182 private RecentsAnimationController mRecentsAnimationController; 183 private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots; 184 185 /** The time when the swipe up gesture is triggered. */ 186 private long mSwipeUpStartTimeMs; 187 188 private boolean mHandlingAtomicEvent; 189 GestureState(OverviewComponentObserver componentObserver, int gestureId)190 public GestureState(OverviewComponentObserver componentObserver, int gestureId) { 191 mHomeIntent = componentObserver.getHomeIntent(); 192 mOverviewIntent = componentObserver.getOverviewIntent(); 193 mActivityInterface = componentObserver.getActivityInterface(); 194 mStateCallback = new MultiStateCallback( 195 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); 196 mGestureId = gestureId; 197 } 198 GestureState(GestureState other)199 public GestureState(GestureState other) { 200 mHomeIntent = other.mHomeIntent; 201 mOverviewIntent = other.mOverviewIntent; 202 mActivityInterface = other.mActivityInterface; 203 mStateCallback = other.mStateCallback; 204 mGestureId = other.mGestureId; 205 mRunningTask = other.mRunningTask; 206 mEndTarget = other.mEndTarget; 207 mLastAppearedTaskTargets = other.mLastAppearedTaskTargets; 208 mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds; 209 mLastStartedTaskId = other.mLastStartedTaskId; 210 } 211 GestureState()212 public GestureState() { 213 // Do nothing, only used for initializing the gesture state prior to user unlock 214 mHomeIntent = new Intent(); 215 mOverviewIntent = new Intent(); 216 mActivityInterface = null; 217 mStateCallback = new MultiStateCallback( 218 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); 219 mGestureId = -1; 220 } 221 222 @Nullable getTrackedEventForState(int stateFlag)223 private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { 224 if (stateFlag == STATE_END_TARGET_ANIMATION_FINISHED) { 225 return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED; 226 } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) { 227 return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED; 228 } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) { 229 return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED; 230 } 231 return null; 232 } 233 234 /** 235 * @return whether the gesture state has the provided {@param stateMask} flags set. 236 */ hasState(int stateMask)237 public boolean hasState(int stateMask) { 238 return mStateCallback.hasStates(stateMask); 239 } 240 241 /** 242 * Sets the given {@param stateFlag}s. 243 */ setState(int stateFlag)244 public void setState(int stateFlag) { 245 mStateCallback.setState(stateFlag); 246 } 247 248 /** 249 * Adds a callback for when the states matching the given {@param stateMask} is set. 250 */ runOnceAtState(int stateMask, Runnable callback)251 public void runOnceAtState(int stateMask, Runnable callback) { 252 mStateCallback.runOnceAtState(stateMask, callback); 253 } 254 255 /** 256 * @return the intent for the Home component. 257 */ getHomeIntent()258 public Intent getHomeIntent() { 259 return mHomeIntent; 260 } 261 262 /** 263 * @return the intent for the Overview component. 264 */ getOverviewIntent()265 public Intent getOverviewIntent() { 266 return mOverviewIntent; 267 } 268 269 /** 270 * @return the interface to the activity handing the UI updates for this gesture. 271 */ 272 public <S extends BaseState<S>, getActivityInterface()273 T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() { 274 return mActivityInterface; 275 } 276 277 /** 278 * @return the id for this particular gesture. 279 */ getGestureId()280 public int getGestureId() { 281 return mGestureId; 282 } 283 284 /** 285 * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger 286 */ setTrackpadGestureType(TrackpadGestureType trackpadGestureType)287 public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) { 288 mTrackpadGestureType = trackpadGestureType; 289 } 290 isTrackpadGesture()291 public boolean isTrackpadGesture() { 292 return mTrackpadGestureType != TrackpadGestureType.NONE; 293 } 294 isThreeFingerTrackpadGesture()295 public boolean isThreeFingerTrackpadGesture() { 296 return mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; 297 } 298 isFourFingerTrackpadGesture()299 public boolean isFourFingerTrackpadGesture() { 300 return mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER; 301 } 302 303 /** 304 * @return the running task for this gesture. 305 */ getRunningTask()306 public CachedTaskInfo getRunningTask() { 307 return mRunningTask; 308 } 309 310 /** 311 * @param getMultipleTasks Whether multiple tasks or not are to be returned (for split) 312 * @return the running task ids for this gesture. 313 */ getRunningTaskIds(boolean getMultipleTasks)314 public int[] getRunningTaskIds(boolean getMultipleTasks) { 315 if (mRunningTask == null) { 316 return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 317 } else { 318 int cachedTasksSize = mRunningTask.mAllCachedTasks.size(); 319 int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1); 320 int[] runningTaskIds = new int[count]; 321 for (int i = 0; i < count; i++) { 322 runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId; 323 } 324 return runningTaskIds; 325 } 326 } 327 328 /** 329 * @see #getRunningTaskIds(boolean) 330 * @return the single top-most running taskId for this gesture 331 */ getTopRunningTaskId()332 public int getTopRunningTaskId() { 333 return getRunningTaskIds(false /*getMultipleTasks*/)[0]; 334 } 335 336 /** 337 * Updates the running task for the gesture to be the given {@param runningTask}. 338 */ updateRunningTask(CachedTaskInfo runningTask)339 public void updateRunningTask(CachedTaskInfo runningTask) { 340 mRunningTask = runningTask; 341 } 342 343 /** 344 * Updates the last task that appeared during this gesture. 345 */ updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets)346 public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) { 347 mLastAppearedTaskTargets = lastAppearedTaskTargets; 348 for (RemoteAnimationTarget target : lastAppearedTaskTargets) { 349 if (target == null) { 350 continue; 351 } 352 mPreviouslyAppearedTaskIds.add(target.taskId); 353 } 354 } 355 356 /** 357 * @return The id of the task that appeared during this gesture. 358 */ getLastAppearedTaskIds()359 public int[] getLastAppearedTaskIds() { 360 if (mLastAppearedTaskTargets == null) { 361 return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 362 } else { 363 return Arrays.stream(mLastAppearedTaskTargets) 364 .mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray(); 365 } 366 } 367 updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds)368 public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) { 369 mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds; 370 } 371 getPreviouslyAppearedTaskIds()372 public Set<Integer> getPreviouslyAppearedTaskIds() { 373 return mPreviouslyAppearedTaskIds; 374 } 375 376 /** 377 * Updates the last task that we started via startActivityFromRecents() during this gesture. 378 */ updateLastStartedTaskIds(int[] lastStartedTaskId)379 public void updateLastStartedTaskIds(int[] lastStartedTaskId) { 380 mLastStartedTaskId = lastStartedTaskId; 381 } 382 383 /** 384 * @return The id of the task that was most recently started during this gesture, or -1 if 385 * no task has been started yet (i.e. we haven't settled on a new task). 386 */ getLastStartedTaskIds()387 public int[] getLastStartedTaskIds() { 388 return mLastStartedTaskId; 389 } 390 391 /** 392 * @return the end target for this gesture (if known). 393 */ getEndTarget()394 public GestureEndTarget getEndTarget() { 395 return mEndTarget; 396 } 397 398 /** 399 * Sets the end target of this gesture and immediately notifies the state changes. 400 */ setEndTarget(GestureEndTarget target)401 public void setEndTarget(GestureEndTarget target) { 402 setEndTarget(target, true /* isAtomic */); 403 } 404 405 /** 406 * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the 407 * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves. 408 */ setEndTarget(GestureEndTarget target, boolean isAtomic)409 public void setEndTarget(GestureEndTarget target, boolean isAtomic) { 410 mEndTarget = target; 411 mStateCallback.setState(STATE_END_TARGET_SET); 412 ActiveGestureLog.INSTANCE.addLog( 413 /* event= */ "setEndTarget " + mEndTarget, 414 /* gestureEvent= */ SET_END_TARGET); 415 switch (mEndTarget) { 416 case HOME: 417 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME); 418 break; 419 case NEW_TASK: 420 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK); 421 break; 422 case ALL_APPS: 423 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_ALL_APPS); 424 break; 425 case LAST_TASK: 426 case RECENTS: 427 default: 428 // No-Op 429 } 430 if (isAtomic) { 431 mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED); 432 } 433 } 434 435 /** 436 * Indicates if the gesture is handling an atomic event like a click and not a 437 * user controlled gesture. 438 */ setHandlingAtomicEvent(boolean handlingAtomicEvent)439 public void setHandlingAtomicEvent(boolean handlingAtomicEvent) { 440 mHandlingAtomicEvent = handlingAtomicEvent; 441 } 442 443 /** 444 * Returns true if the gesture is handling an atomic event like a click and not a 445 * user controlled gesture. 446 */ isHandlingAtomicEvent()447 public boolean isHandlingAtomicEvent() { 448 return mHandlingAtomicEvent; 449 } 450 451 /** 452 * @return whether the current gesture is still running a recents animation to a state in the 453 * Launcher or Recents activity. 454 */ isRunningAnimationToLauncher()455 public boolean isRunningAnimationToLauncher() { 456 return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher; 457 } 458 459 /** 460 * @return whether the recents animation is started but not yet ended 461 */ isRecentsAnimationRunning()462 public boolean isRecentsAnimationRunning() { 463 return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_STARTED) 464 && !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED); 465 } 466 467 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)468 public void onRecentsAnimationStart(RecentsAnimationController controller, 469 RecentsAnimationTargets targets) { 470 mRecentsAnimationController = controller; 471 mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED); 472 } 473 474 @Override onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)475 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 476 mRecentsAnimationCanceledSnapshots = thumbnailDatas; 477 mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED); 478 mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); 479 if (mRecentsAnimationCanceledSnapshots != null) { 480 // Clean up the screenshot to finalize the recents animation cancel 481 if (mRecentsAnimationController != null) { 482 mRecentsAnimationController.cleanupScreenshot(); 483 } 484 mRecentsAnimationCanceledSnapshots = null; 485 } 486 } 487 488 @Override onRecentsAnimationFinished(RecentsAnimationController controller)489 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 490 mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED); 491 mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); 492 } 493 494 /** 495 * Returns and clears the canceled animation thumbnail data. This call only returns a value 496 * while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for 497 * calling {@link RecentsAnimationController#cleanupScreenshot()}. 498 */ 499 @Nullable consumeRecentsAnimationCanceledSnapshot()500 HashMap<Integer, ThumbnailData> consumeRecentsAnimationCanceledSnapshot() { 501 if (mRecentsAnimationCanceledSnapshots != null) { 502 HashMap<Integer, ThumbnailData> data = 503 new HashMap<Integer, ThumbnailData>(mRecentsAnimationCanceledSnapshots); 504 mRecentsAnimationCanceledSnapshots = null; 505 return data; 506 } 507 return null; 508 } 509 setSwipeUpStartTimeMs(long uptimeMs)510 void setSwipeUpStartTimeMs(long uptimeMs) { 511 mSwipeUpStartTimeMs = uptimeMs; 512 } 513 getSwipeUpStartTimeMs()514 long getSwipeUpStartTimeMs() { 515 return mSwipeUpStartTimeMs; 516 } 517 dump(PrintWriter pw)518 public void dump(PrintWriter pw) { 519 pw.println("GestureState:"); 520 pw.println(" gestureID=" + mGestureId); 521 pw.println(" runningTask=" + mRunningTask); 522 pw.println(" endTarget=" + mEndTarget); 523 pw.println(" lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets)); 524 pw.println(" lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId)); 525 pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning()); 526 } 527 } 528