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.content.Intent; 33 import android.os.SystemClock; 34 import android.view.MotionEvent; 35 import android.view.RemoteAnimationTarget; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 40 import com.android.launcher3.statemanager.BaseState; 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.quickstep.views.RecentsViewContainer; 45 import com.android.systemui.shared.recents.model.ThumbnailData; 46 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Set; 54 import java.util.function.Predicate; 55 56 /** 57 * Manages the state for an active system gesture, listens for events from the system and Launcher, 58 * and fires events when the states change. 59 */ 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 BaseContainerInterface mContainerInterface; 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 final long mSwipeUpStartTimeMs = SystemClock.uptimeMillis(); 187 188 private boolean mHandlingAtomicEvent; 189 private boolean mIsInExtendedSlopRegion; 190 GestureState(OverviewComponentObserver componentObserver, int gestureId)191 public GestureState(OverviewComponentObserver componentObserver, int gestureId) { 192 mHomeIntent = componentObserver.getHomeIntent(); 193 mOverviewIntent = componentObserver.getOverviewIntent(); 194 mContainerInterface = componentObserver.getActivityInterface(); 195 mStateCallback = new MultiStateCallback( 196 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); 197 mGestureId = gestureId; 198 } 199 GestureState(GestureState other)200 public GestureState(GestureState other) { 201 mHomeIntent = other.mHomeIntent; 202 mOverviewIntent = other.mOverviewIntent; 203 mContainerInterface = other.mContainerInterface; 204 mStateCallback = other.mStateCallback; 205 mGestureId = other.mGestureId; 206 mRunningTask = other.mRunningTask; 207 mEndTarget = other.mEndTarget; 208 mLastAppearedTaskTargets = other.mLastAppearedTaskTargets; 209 mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds; 210 mLastStartedTaskId = other.mLastStartedTaskId; 211 } 212 GestureState()213 public GestureState() { 214 // Do nothing, only used for initializing the gesture state prior to user unlock 215 mHomeIntent = new Intent(); 216 mOverviewIntent = new Intent(); 217 mContainerInterface = null; 218 mStateCallback = new MultiStateCallback( 219 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); 220 mGestureId = -1; 221 } 222 223 @Nullable getTrackedEventForState(int stateFlag)224 private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { 225 if (stateFlag == STATE_END_TARGET_ANIMATION_FINISHED) { 226 return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED; 227 } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) { 228 return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED; 229 } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) { 230 return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED; 231 } 232 return null; 233 } 234 235 /** 236 * @return whether the gesture state has the provided {@param stateMask} flags set. 237 */ hasState(int stateMask)238 public boolean hasState(int stateMask) { 239 return mStateCallback.hasStates(stateMask); 240 } 241 242 /** 243 * Sets the given {@param stateFlag}s. 244 */ setState(int stateFlag)245 public void setState(int stateFlag) { 246 mStateCallback.setState(stateFlag); 247 } 248 249 /** 250 * Adds a callback for when the states matching the given {@param stateMask} is set. 251 */ runOnceAtState(int stateMask, Runnable callback)252 public void runOnceAtState(int stateMask, Runnable callback) { 253 mStateCallback.runOnceAtState(stateMask, callback); 254 } 255 256 /** 257 * @return the intent for the Home component. 258 */ getHomeIntent()259 public Intent getHomeIntent() { 260 return mHomeIntent; 261 } 262 263 /** 264 * @return the intent for the Overview component. 265 */ getOverviewIntent()266 public Intent getOverviewIntent() { 267 return mOverviewIntent; 268 } 269 270 /** 271 * @return the interface to the activity handing the UI updates for this gesture. 272 */ 273 public <S extends BaseState<S>, T extends RecentsViewContainer> getContainerInterface()274 BaseContainerInterface<S, T> getContainerInterface() { 275 return mContainerInterface; 276 } 277 278 /** 279 * @return the id for this particular gesture. 280 */ getGestureId()281 public int getGestureId() { 282 return mGestureId; 283 } 284 285 /** 286 * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger 287 */ setTrackpadGestureType(TrackpadGestureType trackpadGestureType)288 public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) { 289 mTrackpadGestureType = trackpadGestureType; 290 } 291 isTrackpadGesture()292 public boolean isTrackpadGesture() { 293 return mTrackpadGestureType != TrackpadGestureType.NONE; 294 } 295 isThreeFingerTrackpadGesture()296 public boolean isThreeFingerTrackpadGesture() { 297 return mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; 298 } 299 isFourFingerTrackpadGesture()300 public boolean isFourFingerTrackpadGesture() { 301 return mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER; 302 } 303 304 /** 305 * @return the running task for this gesture. 306 */ 307 @Nullable getRunningTask()308 public CachedTaskInfo getRunningTask() { 309 return mRunningTask; 310 } 311 312 /** 313 * @param getMultipleTasks Whether multiple tasks or not are to be returned (for split) 314 * @return the running task ids for this gesture. 315 */ getRunningTaskIds(boolean getMultipleTasks)316 public int[] getRunningTaskIds(boolean getMultipleTasks) { 317 if (mRunningTask == null) { 318 return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 319 } else { 320 int cachedTasksSize = mRunningTask.mAllCachedTasks.size(); 321 int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1); 322 int[] runningTaskIds = new int[count]; 323 for (int i = 0; i < count; i++) { 324 runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId; 325 } 326 return runningTaskIds; 327 } 328 } 329 330 /** 331 * @see #getRunningTaskIds(boolean) 332 * @return the single top-most running taskId for this gesture 333 */ getTopRunningTaskId()334 public int getTopRunningTaskId() { 335 return getRunningTaskIds(false /*getMultipleTasks*/)[0]; 336 } 337 338 /** 339 * Updates the running task for the gesture to be the given {@param runningTask}. 340 */ updateRunningTask(@onNull CachedTaskInfo runningTask)341 public void updateRunningTask(@NonNull CachedTaskInfo runningTask) { 342 mRunningTask = runningTask; 343 } 344 345 /** 346 * Updates the last task that appeared during this gesture. 347 */ updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets)348 public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) { 349 mLastAppearedTaskTargets = lastAppearedTaskTargets; 350 for (RemoteAnimationTarget target : lastAppearedTaskTargets) { 351 if (target == null) { 352 continue; 353 } 354 mPreviouslyAppearedTaskIds.add(target.taskId); 355 } 356 } 357 358 /** 359 * @return The id of the task that appeared during this gesture. 360 */ getLastAppearedTaskIds()361 public int[] getLastAppearedTaskIds() { 362 if (mLastAppearedTaskTargets == null) { 363 return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 364 } else { 365 return Arrays.stream(mLastAppearedTaskTargets) 366 .mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray(); 367 } 368 } 369 updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds)370 public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) { 371 mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds; 372 } 373 getPreviouslyAppearedTaskIds()374 public Set<Integer> getPreviouslyAppearedTaskIds() { 375 return mPreviouslyAppearedTaskIds; 376 } 377 378 /** 379 * Updates the last task that we started via startActivityFromRecents() during this gesture. 380 */ updateLastStartedTaskIds(int[] lastStartedTaskId)381 public void updateLastStartedTaskIds(int[] lastStartedTaskId) { 382 mLastStartedTaskId = lastStartedTaskId; 383 } 384 385 /** 386 * @return The id of the task that was most recently started during this gesture, or -1 if 387 * no task has been started yet (i.e. we haven't settled on a new task). 388 */ getLastStartedTaskIds()389 public int[] getLastStartedTaskIds() { 390 return mLastStartedTaskId; 391 } 392 393 /** 394 * @return the end target for this gesture (if known). 395 */ getEndTarget()396 public GestureEndTarget getEndTarget() { 397 return mEndTarget; 398 } 399 400 /** 401 * Sets the end target of this gesture and immediately notifies the state changes. 402 */ setEndTarget(GestureEndTarget target)403 public void setEndTarget(GestureEndTarget target) { 404 setEndTarget(target, true /* isAtomic */); 405 } 406 407 /** 408 * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the 409 * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves. 410 */ setEndTarget(GestureEndTarget target, boolean isAtomic)411 public void setEndTarget(GestureEndTarget target, boolean isAtomic) { 412 mEndTarget = target; 413 mStateCallback.setState(STATE_END_TARGET_SET); 414 ActiveGestureLog.INSTANCE.addLog( 415 new ActiveGestureLog.CompoundString("setEndTarget ") 416 .append(mEndTarget.name()), 417 /* gestureEvent= */ SET_END_TARGET); 418 switch (mEndTarget) { 419 case HOME: 420 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME); 421 break; 422 case NEW_TASK: 423 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK); 424 break; 425 case ALL_APPS: 426 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_ALL_APPS); 427 break; 428 case LAST_TASK: 429 case RECENTS: 430 default: 431 // No-Op 432 } 433 if (isAtomic) { 434 mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED); 435 } 436 } 437 438 /** 439 * Indicates if the gesture is handling an atomic event like a click and not a 440 * user controlled gesture. 441 */ setHandlingAtomicEvent(boolean handlingAtomicEvent)442 public void setHandlingAtomicEvent(boolean handlingAtomicEvent) { 443 mHandlingAtomicEvent = handlingAtomicEvent; 444 } 445 446 /** 447 * Returns true if the gesture is handling an atomic event like a click and not a 448 * user controlled gesture. 449 */ isHandlingAtomicEvent()450 public boolean isHandlingAtomicEvent() { 451 return mHandlingAtomicEvent; 452 } 453 454 /** 455 * @return whether the current gesture is still running a recents animation to a state in the 456 * Launcher or Recents activity. 457 */ isRunningAnimationToLauncher()458 public boolean isRunningAnimationToLauncher() { 459 return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher; 460 } 461 462 /** 463 * @return whether the recents animation is started but not yet ended 464 */ isRecentsAnimationRunning()465 public boolean isRecentsAnimationRunning() { 466 return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_STARTED) 467 && !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED); 468 } 469 470 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets)471 public void onRecentsAnimationStart(RecentsAnimationController controller, 472 RecentsAnimationTargets targets) { 473 mRecentsAnimationController = controller; 474 mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED); 475 } 476 477 @Override onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)478 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 479 mRecentsAnimationCanceledSnapshots = thumbnailDatas; 480 mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED); 481 mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); 482 if (mRecentsAnimationCanceledSnapshots != null) { 483 // Clean up the screenshot to finalize the recents animation cancel 484 if (mRecentsAnimationController != null) { 485 mRecentsAnimationController.cleanupScreenshot(); 486 } 487 mRecentsAnimationCanceledSnapshots = null; 488 } 489 } 490 491 @Override onRecentsAnimationFinished(RecentsAnimationController controller)492 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 493 mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED); 494 mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); 495 } 496 497 /** 498 * Set whether it's in long press nav handle (LPNH)'s extended touch slop region, e.g., second 499 * stage region in order to continue respect LPNH and ignore other touch slop logic. 500 * This will only be set to true when flag ENABLE_LPNH_TWO_STAGES is turned on. 501 */ setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion)502 public void setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion) { 503 if (DeviceConfigWrapper.get().getEnableLpnhTwoStages()) { 504 mIsInExtendedSlopRegion = isInExtendedSlopRegion; 505 } 506 } 507 508 /** 509 * Returns whether it's in LPNH's extended touch slop region. This is only valid when flag 510 * ENABLE_LPNH_TWO_STAGES is turned on. 511 */ isInExtendedSlopRegion()512 public boolean isInExtendedSlopRegion() { 513 return mIsInExtendedSlopRegion; 514 } 515 516 /** 517 * Returns and clears the canceled animation thumbnail data. This call only returns a value 518 * while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for 519 * calling {@link RecentsAnimationController#cleanupScreenshot()}. 520 */ 521 @Nullable consumeRecentsAnimationCanceledSnapshot()522 HashMap<Integer, ThumbnailData> consumeRecentsAnimationCanceledSnapshot() { 523 if (mRecentsAnimationCanceledSnapshots != null) { 524 HashMap<Integer, ThumbnailData> data = 525 new HashMap<Integer, ThumbnailData>(mRecentsAnimationCanceledSnapshots); 526 mRecentsAnimationCanceledSnapshots = null; 527 return data; 528 } 529 return null; 530 } 531 getSwipeUpStartTimeMs()532 long getSwipeUpStartTimeMs() { 533 return mSwipeUpStartTimeMs; 534 } 535 dump(String prefix, PrintWriter pw)536 public void dump(String prefix, PrintWriter pw) { 537 pw.println(prefix + "GestureState:"); 538 pw.println(prefix + "\tgestureID=" + mGestureId); 539 pw.println(prefix + "\trunningTask=" + mRunningTask); 540 pw.println(prefix + "\tendTarget=" + mEndTarget); 541 pw.println(prefix + "\tlastAppearedTaskTargetId=" 542 + Arrays.toString(mLastAppearedTaskTargets)); 543 pw.println(prefix + "\tlastStartedTaskId=" + Arrays.toString(mLastStartedTaskId)); 544 pw.println(prefix + "\tisRecentsAnimationRunning=" + isRecentsAnimationRunning()); 545 } 546 } 547