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.BaseActivity.INVISIBLE_BY_STATE_HANDLER; 19 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; 20 import static com.android.launcher3.anim.Interpolators.DEACCEL; 21 import static com.android.launcher3.anim.Interpolators.LINEAR; 22 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; 23 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 24 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; 25 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; 26 import static com.android.launcher3.util.RaceConditionTracker.ENTER; 27 import static com.android.launcher3.util.RaceConditionTracker.EXIT; 28 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; 29 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; 30 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG; 31 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME; 32 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK; 33 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK; 34 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS; 35 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE; 36 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; 37 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; 38 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; 39 40 import android.animation.Animator; 41 import android.animation.AnimatorSet; 42 import android.animation.TimeInterpolator; 43 import android.animation.ValueAnimator; 44 import android.annotation.TargetApi; 45 import android.app.ActivityManager.RunningTaskInfo; 46 import android.content.Context; 47 import android.content.Intent; 48 import android.graphics.Canvas; 49 import android.graphics.PointF; 50 import android.graphics.RectF; 51 import android.os.Build; 52 import android.os.SystemClock; 53 import android.view.View; 54 import android.view.View.OnApplyWindowInsetsListener; 55 import android.view.ViewTreeObserver.OnDrawListener; 56 import android.view.WindowInsets; 57 import android.view.animation.Interpolator; 58 59 import androidx.annotation.NonNull; 60 import androidx.annotation.UiThread; 61 62 import com.android.launcher3.AbstractFloatingView; 63 import com.android.launcher3.BaseDraggingActivity; 64 import com.android.launcher3.DeviceProfile; 65 import com.android.launcher3.R; 66 import com.android.launcher3.Utilities; 67 import com.android.launcher3.anim.AnimationSuccessListener; 68 import com.android.launcher3.anim.AnimatorPlaybackController; 69 import com.android.launcher3.anim.Interpolators; 70 import com.android.launcher3.logging.UserEventDispatcher; 71 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; 72 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; 73 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 74 import com.android.launcher3.util.RaceConditionTracker; 75 import com.android.launcher3.util.TraceHelper; 76 import com.android.quickstep.ActivityControlHelper.AnimationFactory; 77 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory; 78 import com.android.quickstep.SysUINavigationMode.Mode; 79 import com.android.quickstep.inputconsumers.InputConsumer; 80 import com.android.quickstep.inputconsumers.OverviewInputConsumer; 81 import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider; 82 import com.android.quickstep.util.RectFSpringAnim; 83 import com.android.quickstep.util.ShelfPeekAnim; 84 import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; 85 import com.android.quickstep.util.SwipeAnimationTargetSet; 86 import com.android.quickstep.views.LiveTileOverlay; 87 import com.android.quickstep.views.RecentsView; 88 import com.android.quickstep.views.TaskView; 89 import com.android.systemui.shared.recents.model.ThumbnailData; 90 import com.android.systemui.shared.system.InputConsumerController; 91 import com.android.systemui.shared.system.LatencyTrackerCompat; 92 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 93 import com.android.systemui.shared.system.WindowCallbacksCompat; 94 95 @TargetApi(Build.VERSION_CODES.O) 96 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 97 extends BaseSwipeUpHandler<T, RecentsView> 98 implements OnApplyWindowInsetsListener { 99 private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName(); 100 101 private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; 102 getFlagForIndex(int index, String name)103 private static int getFlagForIndex(int index, String name) { 104 if (DEBUG_STATES) { 105 STATE_NAMES[index] = name; 106 } 107 return 1 << index; 108 } 109 110 // Launcher UI related states 111 private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT"); 112 private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED"); 113 private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN"); 114 115 // Internal initialization states 116 private static final int STATE_APP_CONTROLLER_RECEIVED = 117 getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED"); 118 119 // Interaction finish states 120 private static final int STATE_SCALED_CONTROLLER_HOME = 121 getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME"); 122 private static final int STATE_SCALED_CONTROLLER_RECENTS = 123 getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS"); 124 125 private static final int STATE_HANDLER_INVALIDATED = 126 getFlagForIndex(6, "STATE_HANDLER_INVALIDATED"); 127 private static final int STATE_GESTURE_STARTED = 128 getFlagForIndex(7, "STATE_GESTURE_STARTED"); 129 private static final int STATE_GESTURE_CANCELLED = 130 getFlagForIndex(8, "STATE_GESTURE_CANCELLED"); 131 private static final int STATE_GESTURE_COMPLETED = 132 getFlagForIndex(9, "STATE_GESTURE_COMPLETED"); 133 134 private static final int STATE_CAPTURE_SCREENSHOT = 135 getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT"); 136 private static final int STATE_SCREENSHOT_CAPTURED = 137 getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED"); 138 private static final int STATE_SCREENSHOT_VIEW_SHOWN = 139 getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN"); 140 141 private static final int STATE_RESUME_LAST_TASK = 142 getFlagForIndex(13, "STATE_RESUME_LAST_TASK"); 143 private static final int STATE_START_NEW_TASK = 144 getFlagForIndex(14, "STATE_START_NEW_TASK"); 145 private static final int STATE_CURRENT_TASK_FINISHED = 146 getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED"); 147 148 private static final int LAUNCHER_UI_STATES = 149 STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; 150 151 public enum GestureEndTarget { 152 HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false, 153 ContainerType.WORKSPACE, false), 154 155 RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT 156 | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true), 157 158 NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true, 159 ContainerType.APP, true), 160 161 LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false); 162 GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, int containerType, boolean recentsAttachedToAppWindow)163 GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, 164 int containerType, boolean recentsAttachedToAppWindow) { 165 this.endShift = endShift; 166 this.endState = endState; 167 this.isLauncher = isLauncher; 168 this.canBeContinued = canBeContinued; 169 this.containerType = containerType; 170 this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; 171 } 172 173 /** 0 is app, 1 is overview */ 174 public final float endShift; 175 /** The state to apply when we reach this final target */ 176 public final int endState; 177 /** Whether the target is in the launcher activity */ 178 public final boolean isLauncher; 179 /** Whether the user can start a new gesture while this one is finishing */ 180 public final boolean canBeContinued; 181 /** Used to log where the user ended up after the gesture ends */ 182 public final int containerType; 183 /** Whether RecentsView should be attached to the window as we animate to this target */ 184 public final boolean recentsAttachedToAppWindow; 185 } 186 187 public static final long MAX_SWIPE_DURATION = 350; 188 public static final long MIN_SWIPE_DURATION = 80; 189 public static final long MIN_OVERSHOOT_DURATION = 120; 190 191 public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f; 192 private static final float SWIPE_DURATION_MULTIPLIER = 193 Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW)); 194 private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured"; 195 196 public static final long RECENTS_ATTACH_DURATION = 300; 197 198 /** 199 * Used as the page index for logging when we return to the last task at the end of the gesture. 200 */ 201 private static final int LOG_NO_OP_PAGE_INDEX = -1; 202 203 private GestureEndTarget mGestureEndTarget; 204 // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise 205 private RunningWindowAnim mRunningWindowAnim; 206 private boolean mIsShelfPeeking; 207 208 private boolean mContinuingLastGesture; 209 210 private ThumbnailData mTaskSnapshot; 211 212 // Used to control launcher components throughout the swipe gesture. 213 private AnimatorPlaybackController mLauncherTransitionController; 214 private boolean mHasLauncherTransitionControllerStarted; 215 216 private AnimationFactory mAnimationFactory = (t) -> { }; 217 private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay(); 218 private boolean mLiveTileOverlayAttached = false; 219 220 private boolean mWasLauncherAlreadyVisible; 221 222 private boolean mPassedOverviewThreshold; 223 private boolean mGestureStarted; 224 private int mLogAction = Touch.SWIPE; 225 private int mLogDirection = Direction.UP; 226 private PointF mDownPos; 227 private boolean mIsLikelyToStartNewTask; 228 229 private final long mTouchTimeMs; 230 private long mLauncherFrameDrawnTime; 231 WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs, OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture, InputConsumerController inputConsumer, RecentsModel recentsModel)232 public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, 233 long touchTimeMs, OverviewComponentObserver overviewComponentObserver, 234 boolean continuingLastGesture, 235 InputConsumerController inputConsumer, RecentsModel recentsModel) { 236 super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id); 237 mTouchTimeMs = touchTimeMs; 238 mContinuingLastGesture = continuingLastGesture; 239 initStateCallbacks(); 240 } 241 initStateCallbacks()242 private void initStateCallbacks() { 243 mStateCallback = new MultiStateCallback(STATE_NAMES); 244 245 mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, 246 this::onLauncherPresentAndGestureStarted); 247 248 mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED, 249 this::initializeLauncherAnimationController); 250 251 mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN, 252 this::launcherFrameDrawn); 253 254 mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED 255 | STATE_GESTURE_CANCELLED, 256 this::resetStateForAnimationCancel); 257 258 mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED, 259 this::sendRemoteAnimationsToAnimationFactory); 260 261 mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED, 262 this::resumeLastTask); 263 mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED, 264 this::startNewTask); 265 266 mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED 267 | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT, 268 this::switchToScreenshot); 269 270 mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED 271 | STATE_SCALED_CONTROLLER_RECENTS, 272 this::finishCurrentTransitionToRecents); 273 274 mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED 275 | STATE_SCALED_CONTROLLER_HOME, 276 this::finishCurrentTransitionToHome); 277 mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, 278 this::reset); 279 280 mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED 281 | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS 282 | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED 283 | STATE_GESTURE_STARTED, 284 this::setupLauncherUiAfterSwipeUpToRecentsAnimation); 285 286 mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler); 287 mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, 288 this::invalidateHandlerWithLauncher); 289 mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, 290 this::notifyTransitionCancelled); 291 292 mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, 293 mRecentsAnimationWrapper::enableInputConsumer); 294 295 if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { 296 mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT 297 | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT, 298 (b) -> mRecentsView.setRunningTaskHidden(!b)); 299 } 300 } 301 302 @Override onActivityInit(final T activity, Boolean alreadyOnHome)303 protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) { 304 if (mActivity == activity) { 305 return true; 306 } 307 if (mActivity != null) { 308 // The launcher may have been recreated as a result of device rotation. 309 int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES; 310 initStateCallbacks(); 311 mStateCallback.setState(oldState); 312 } 313 mWasLauncherAlreadyVisible = alreadyOnHome; 314 mActivity = activity; 315 // Override the visibility of the activity until the gesture actually starts and we swipe 316 // up, or until we transition home and the home animation is composed 317 if (alreadyOnHome) { 318 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 319 } else { 320 mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 321 } 322 323 mRecentsView = activity.getOverviewPanel(); 324 linkRecentsViewScroll(); 325 addLiveTileOverlay(); 326 327 mStateCallback.setState(STATE_LAUNCHER_PRESENT); 328 if (alreadyOnHome) { 329 onLauncherStart(activity); 330 } else { 331 activity.setOnStartCallback(this::onLauncherStart); 332 } 333 334 setupRecentsViewUi(); 335 return true; 336 } 337 338 @Override moveWindowWithRecentsScroll()339 protected boolean moveWindowWithRecentsScroll() { 340 return mGestureEndTarget != HOME; 341 } 342 onLauncherStart(final T activity)343 private void onLauncherStart(final T activity) { 344 if (mActivity != activity) { 345 return; 346 } 347 if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { 348 return; 349 } 350 351 // If we've already ended the gesture and are going home, don't prepare recents UI, 352 // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL. 353 if (mGestureEndTarget != HOME) { 354 Runnable initAnimFactory = () -> { 355 mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity, 356 mWasLauncherAlreadyVisible, true, 357 this::onAnimatorPlaybackControllerCreated); 358 maybeUpdateRecentsAttachedState(false /* animate */); 359 }; 360 if (mWasLauncherAlreadyVisible) { 361 // Launcher is visible, but might be about to stop. Thus, if we prepare recents 362 // now, it might get overridden by moveToRestState() in onStop(). To avoid this, 363 // wait until the next gesture (and possibly launcher) starts. 364 mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory); 365 } else { 366 initAnimFactory.run(); 367 } 368 } 369 AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible, 370 AbstractFloatingView.TYPE_LISTENER); 371 372 if (mWasLauncherAlreadyVisible) { 373 mStateCallback.setState(STATE_LAUNCHER_DRAWN); 374 } else { 375 TraceHelper.beginSection("WTS-init"); 376 View dragLayer = activity.getDragLayer(); 377 dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() { 378 379 @Override 380 public void onDraw() { 381 TraceHelper.endSection("WTS-init", "Launcher frame is drawn"); 382 dragLayer.post(() -> 383 dragLayer.getViewTreeObserver().removeOnDrawListener(this)); 384 if (activity != mActivity) { 385 return; 386 } 387 388 mStateCallback.setState(STATE_LAUNCHER_DRAWN); 389 } 390 }); 391 } 392 393 activity.getRootView().setOnApplyWindowInsetsListener(this); 394 mStateCallback.setState(STATE_LAUNCHER_STARTED); 395 } 396 onLauncherPresentAndGestureStarted()397 private void onLauncherPresentAndGestureStarted() { 398 // Re-setup the recents UI when gesture starts, as the state could have been changed during 399 // that time by a previous window transition. 400 setupRecentsViewUi(); 401 402 notifyGestureStartedAsync(); 403 } 404 setupRecentsViewUi()405 private void setupRecentsViewUi() { 406 if (mContinuingLastGesture) { 407 updateSysUiFlags(mCurrentShift.value); 408 return; 409 } 410 mRecentsView.onGestureAnimationStart(mRunningTaskId); 411 } 412 launcherFrameDrawn()413 private void launcherFrameDrawn() { 414 mLauncherFrameDrawnTime = SystemClock.uptimeMillis(); 415 } 416 sendRemoteAnimationsToAnimationFactory()417 private void sendRemoteAnimationsToAnimationFactory() { 418 mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet); 419 } 420 initializeLauncherAnimationController()421 private void initializeLauncherAnimationController() { 422 buildAnimationController(); 423 424 if (LatencyTrackerCompat.isEnabled(mContext)) { 425 LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs)); 426 } 427 428 // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the 429 // high-res thumbnail loader here once we are sure that we will end up in an overview state 430 RecentsModel.INSTANCE.get(mContext).getThumbnailCache() 431 .getHighResLoadingState().setVisible(true); 432 } 433 getTaskCurveScaleForOffsetX(float offsetX, float taskWidth)434 private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) { 435 float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + 436 mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); 437 float interpolation = Math.min(1, offsetX / distanceToReachEdge); 438 return TaskView.getCurveScaleForInterpolation(interpolation); 439 } 440 441 @Override onMotionPauseChanged(boolean isPaused)442 public void onMotionPauseChanged(boolean isPaused) { 443 setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION); 444 } 445 maybeUpdateRecentsAttachedState()446 public void maybeUpdateRecentsAttachedState() { 447 maybeUpdateRecentsAttachedState(true /* animate */); 448 } 449 450 /** 451 * Determines whether to show or hide RecentsView. The window is always 452 * synchronized with its corresponding TaskView in RecentsView, so if 453 * RecentsView is shown, it will appear to be attached to the window. 454 * 455 * Note this method has no effect unless the navigation mode is NO_BUTTON. 456 */ maybeUpdateRecentsAttachedState(boolean animate)457 private void maybeUpdateRecentsAttachedState(boolean animate) { 458 if (mMode != Mode.NO_BUTTON || mRecentsView == null) { 459 return; 460 } 461 RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null 462 ? null 463 : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId); 464 final boolean recentsAttachedToAppWindow; 465 int runningTaskIndex = mRecentsView.getRunningTaskIndex(); 466 if (mGestureEndTarget != null) { 467 recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow; 468 } else if (mContinuingLastGesture 469 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) { 470 recentsAttachedToAppWindow = true; 471 animate = false; 472 } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) { 473 // The window is going away so make sure recents is always visible in this case. 474 recentsAttachedToAppWindow = true; 475 animate = false; 476 } else { 477 recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask; 478 if (animate) { 479 // Only animate if an adjacent task view is visible on screen. 480 TaskView adjacentTask1 = mRecentsView.getNextTaskView(); 481 TaskView adjacentTask2 = mRecentsView.getPreviousTaskView(); 482 float prevTranslationX = mRecentsView.getTranslationX(); 483 mRecentsView.setTranslationX(0); 484 animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT)) 485 || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT)); 486 mRecentsView.setTranslationX(prevTranslationX); 487 } 488 } 489 mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate); 490 } 491 492 @Override setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask)493 public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { 494 if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) { 495 mIsLikelyToStartNewTask = isLikelyToStartNewTask; 496 maybeUpdateRecentsAttachedState(); 497 } 498 } 499 500 @UiThread setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration)501 public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { 502 mAnimationFactory.setShelfState(shelfState, interpolator, duration); 503 boolean wasShelfPeeking = mIsShelfPeeking; 504 mIsShelfPeeking = shelfState == PEEK; 505 if (mIsShelfPeeking != wasShelfPeeking) { 506 maybeUpdateRecentsAttachedState(); 507 } 508 if (shelfState.shouldPreformHaptic) { 509 performHapticFeedback(); 510 } 511 } 512 buildAnimationController()513 private void buildAnimationController() { 514 if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) { 515 // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it 516 // has its own animation) or if we're already animating the current controller. 517 return; 518 } 519 initTransitionEndpoints(mActivity.getDeviceProfile()); 520 mAnimationFactory.createActivityController(mTransitionDragLength); 521 } 522 523 @Override onApplyWindowInsets(View view, WindowInsets windowInsets)524 public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) { 525 WindowInsets result = view.onApplyWindowInsets(windowInsets); 526 buildAnimationController(); 527 return result; 528 } 529 onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim)530 private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) { 531 mLauncherTransitionController = anim; 532 mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor); 533 mAnimationFactory.adjustActivityControllerInterpolators(); 534 mLauncherTransitionController.dispatchOnStart(); 535 updateLauncherTransitionProgress(); 536 } 537 538 @Override getLaunchIntent()539 public Intent getLaunchIntent() { 540 return mOverviewComponentObserver.getOverviewIntent(); 541 } 542 543 @Override updateFinalShift()544 public void updateFinalShift() { 545 546 SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController(); 547 if (controller != null) { 548 applyTransformUnchecked(); 549 updateSysUiFlags(mCurrentShift.value); 550 } 551 552 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 553 if (mRecentsAnimationWrapper.getController() != null) { 554 mLiveTileOverlay.update(mClipAnimationHelper.getCurrentRectWithInsets(), 555 mClipAnimationHelper.getCurrentCornerRadius()); 556 } 557 } 558 559 final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; 560 if (passed != mPassedOverviewThreshold) { 561 mPassedOverviewThreshold = passed; 562 if (mMode != Mode.NO_BUTTON) { 563 performHapticFeedback(); 564 } 565 } 566 567 if (mLauncherTransitionController == null || mLauncherTransitionController 568 .getAnimationPlayer().isStarted()) { 569 return; 570 } 571 updateLauncherTransitionProgress(); 572 } 573 updateLauncherTransitionProgress()574 private void updateLauncherTransitionProgress() { 575 if (mGestureEndTarget == HOME) { 576 return; 577 } 578 // Normalize the progress to 0 to 1, as the animation controller will clamp it to that 579 // anyway. The controller mimics the drag length factor by applying it to its interpolators. 580 float progress = mCurrentShift.value / mDragLengthFactor; 581 mLauncherTransitionController.setPlayFraction(progress); 582 } 583 584 /** 585 * @param windowProgress 0 == app, 1 == overview 586 */ updateSysUiFlags(float windowProgress)587 private void updateSysUiFlags(float windowProgress) { 588 if (mRecentsView != null) { 589 TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen(); 590 int centermostTaskFlags = centermostTask == null ? 0 591 : centermostTask.getThumbnail().getSysUiStatusNavFlags(); 592 boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD; 593 // We will handle the sysui flags based on the centermost task view. 594 mRecentsAnimationWrapper.setWindowThresholdCrossed(centermostTaskFlags != 0 595 || useHomeScreenFlags); 596 int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags; 597 mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags); 598 } 599 } 600 601 @Override onRecentsAnimationStart(SwipeAnimationTargetSet targetSet)602 public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) { 603 super.onRecentsAnimationStart(targetSet); 604 TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length); 605 setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); 606 607 mPassedOverviewThreshold = false; 608 } 609 610 @Override onRecentsAnimationCanceled()611 public void onRecentsAnimationCanceled() { 612 mRecentsAnimationWrapper.setController(null); 613 mActivityInitListener.unregister(); 614 setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); 615 TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation"); 616 } 617 618 @Override onGestureStarted()619 public void onGestureStarted() { 620 notifyGestureStartedAsync(); 621 setStateOnUiThread(STATE_GESTURE_STARTED); 622 mGestureStarted = true; 623 } 624 625 /** 626 * Notifies the launcher that the swipe gesture has started. This can be called multiple times. 627 */ 628 @UiThread notifyGestureStartedAsync()629 private void notifyGestureStartedAsync() { 630 final T curActivity = mActivity; 631 if (curActivity != null) { 632 // Once the gesture starts, we can no longer transition home through the button, so 633 // reset the force override of the activity visibility 634 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 635 } 636 } 637 638 /** 639 * Called as a result on ACTION_CANCEL to return the UI to the start state. 640 */ 641 @Override onGestureCancelled()642 public void onGestureCancelled() { 643 updateDisplacement(0); 644 setStateOnUiThread(STATE_GESTURE_COMPLETED); 645 mLogAction = Touch.SWIPE_NOOP; 646 handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */); 647 } 648 649 /** 650 * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen. 651 * @param velocity The x and y components of the velocity when the gesture ends. 652 * @param downPos The x and y value of where the gesture started. 653 */ 654 @Override onGestureEnded(float endVelocity, PointF velocity, PointF downPos)655 public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) { 656 float flingThreshold = mContext.getResources() 657 .getDimension(R.dimen.quickstep_fling_threshold_velocity); 658 boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold; 659 setStateOnUiThread(STATE_GESTURE_COMPLETED); 660 661 mLogAction = isFling ? Touch.FLING : Touch.SWIPE; 662 boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x); 663 if (isVelocityVertical) { 664 mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN; 665 } else { 666 mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT; 667 } 668 mDownPos = downPos; 669 handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */); 670 } 671 672 @Override 673 protected InputConsumer createNewInputProxyHandler() { 674 endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */); 675 endLauncherTransitionController(); 676 if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { 677 // Hide the task view, if not already hidden 678 setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); 679 } 680 681 BaseDraggingActivity activity = mActivityControlHelper.getCreatedActivity(); 682 return activity == null 683 ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true); 684 } 685 686 private void endRunningWindowAnim(boolean cancel) { 687 if (mRunningWindowAnim != null) { 688 if (cancel) { 689 mRunningWindowAnim.cancel(); 690 } else { 691 mRunningWindowAnim.end(); 692 } 693 } 694 } 695 696 private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling, 697 boolean isCancel) { 698 final GestureEndTarget endTarget; 699 final boolean goingToNewTask; 700 if (mRecentsView != null) { 701 if (!mRecentsAnimationWrapper.hasTargets()) { 702 // If there are no running tasks, then we can assume that this is a continuation of 703 // the last gesture, but after the recents animation has finished 704 goingToNewTask = true; 705 } else { 706 final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); 707 final int taskToLaunch = mRecentsView.getNextPage(); 708 goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex; 709 } 710 } else { 711 goingToNewTask = false; 712 } 713 final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; 714 if (!isFling) { 715 if (isCancel) { 716 endTarget = LAST_TASK; 717 } else if (mMode == Mode.NO_BUTTON) { 718 if (mIsShelfPeeking) { 719 endTarget = RECENTS; 720 } else if (goingToNewTask) { 721 endTarget = NEW_TASK; 722 } else { 723 endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME; 724 } 725 } else { 726 endTarget = reachedOverviewThreshold && mGestureStarted 727 ? RECENTS 728 : goingToNewTask 729 ? NEW_TASK 730 : LAST_TASK; 731 } 732 } else { 733 // If swiping at a diagonal, base end target on the faster velocity. 734 boolean isSwipeUp = endVelocity < 0; 735 boolean willGoToNewTaskOnSwipeUp = 736 goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity); 737 738 if (mMode == Mode.NO_BUTTON && isSwipeUp && !willGoToNewTaskOnSwipeUp) { 739 endTarget = HOME; 740 } else if (mMode == Mode.NO_BUTTON && isSwipeUp && !mIsShelfPeeking) { 741 // If swiping at a diagonal, base end target on the faster velocity. 742 endTarget = NEW_TASK; 743 } else if (isSwipeUp) { 744 endTarget = !reachedOverviewThreshold && willGoToNewTaskOnSwipeUp 745 ? NEW_TASK : RECENTS; 746 } else { 747 endTarget = goingToNewTask ? NEW_TASK : LAST_TASK; 748 } 749 } 750 751 int stateFlags = OverviewInteractionState.INSTANCE.get(mActivity).getSystemUiStateFlags(); 752 if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0 753 && (endTarget == RECENTS || endTarget == LAST_TASK)) { 754 return LAST_TASK; 755 } 756 return endTarget; 757 } 758 759 @UiThread handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, boolean isCancel)760 private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, 761 boolean isCancel) { 762 PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000); 763 long duration = MAX_SWIPE_DURATION; 764 float currentShift = mCurrentShift.value; 765 final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity, 766 isFling, isCancel); 767 float endShift = endTarget.endShift; 768 final float startShift; 769 Interpolator interpolator = DEACCEL; 770 if (!isFling) { 771 long expectedDuration = Math.abs(Math.round((endShift - currentShift) 772 * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); 773 duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); 774 startShift = currentShift; 775 interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL; 776 } else { 777 startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y 778 * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); 779 float minFlingVelocity = mContext.getResources() 780 .getDimension(R.dimen.quickstep_fling_min_velocity); 781 if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { 782 if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) { 783 Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams( 784 startShift, endShift, endShift, endVelocity / 1000, 785 mTransitionDragLength, mContext); 786 endShift = overshoot.end; 787 interpolator = overshoot.interpolator; 788 duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION, 789 MAX_SWIPE_DURATION); 790 } else { 791 float distanceToTravel = (endShift - currentShift) * mTransitionDragLength; 792 793 // we want the page's snap velocity to approximately match the velocity at 794 // which the user flings, so we scale the duration by a value near to the 795 // derivative of the scroll interpolator at zero, ie. 2. 796 long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); 797 duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); 798 799 if (endTarget == RECENTS) { 800 interpolator = OVERSHOOT_1_2; 801 } 802 } 803 } 804 } 805 806 if (endTarget.isLauncher) { 807 mRecentsAnimationWrapper.enableInputProxy(); 808 } 809 810 if (endTarget == HOME) { 811 setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); 812 duration = Math.max(MIN_OVERSHOOT_DURATION, duration); 813 } else if (endTarget == RECENTS) { 814 mLiveTileOverlay.startIconAnimation(); 815 if (mRecentsView != null) { 816 int nearestPage = mRecentsView.getPageNearestToCenterOfScreen(); 817 if (mRecentsView.getNextPage() != nearestPage) { 818 // We shouldn't really scroll to the next page when swiping up to recents. 819 // Only allow settling on the next page if it's nearest to the center. 820 mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration)); 821 } 822 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) { 823 mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION); 824 } 825 duration = Math.max(duration, mRecentsView.getScroller().getDuration()); 826 } 827 if (mMode == Mode.NO_BUTTON) { 828 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration); 829 } 830 } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) { 831 // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() 832 // or resumeLastTask(). 833 if (mRecentsView != null) { 834 duration = Math.max(duration, mRecentsView.getScroller().getDuration()); 835 } 836 } 837 animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs); 838 } 839 doLogGesture(GestureEndTarget endTarget)840 private void doLogGesture(GestureEndTarget endTarget) { 841 DeviceProfile dp = mDp; 842 if (dp == null || mDownPos == null) { 843 // We probably never received an animation controller, skip logging. 844 return; 845 } 846 847 int pageIndex = endTarget == LAST_TASK 848 ? LOG_NO_OP_PAGE_INDEX 849 : mRecentsView.getNextPage(); 850 UserEventDispatcher.newInstance(mContext).logStateChangeAction( 851 mLogAction, mLogDirection, 852 (int) mDownPos.x, (int) mDownPos.y, 853 ContainerType.NAVBAR, ContainerType.APP, 854 endTarget.containerType, 855 pageIndex); 856 } 857 858 /** Animates to the given progress, where 0 is the current app and 1 is overview. */ 859 @UiThread animateToProgress(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs)860 private void animateToProgress(float start, float end, long duration, Interpolator interpolator, 861 GestureEndTarget target, PointF velocityPxPerMs) { 862 mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration, 863 interpolator, target, velocityPxPerMs)); 864 } 865 866 @UiThread animateToProgressInternal(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs)867 private void animateToProgressInternal(float start, float end, long duration, 868 Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) { 869 mGestureEndTarget = target; 870 871 maybeUpdateRecentsAttachedState(); 872 873 if (mGestureEndTarget == HOME) { 874 HomeAnimationFactory homeAnimFactory; 875 if (mActivity != null) { 876 homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity); 877 } else { 878 homeAnimFactory = new HomeAnimationFactory() { 879 @NonNull 880 @Override 881 public RectF getWindowTargetRect() { 882 RectF fallbackTarget = new RectF(mClipAnimationHelper.getTargetRect()); 883 Utilities.scaleRectFAboutCenter(fallbackTarget, 0.25f); 884 return fallbackTarget; 885 } 886 887 @NonNull 888 @Override 889 public AnimatorPlaybackController createActivityAnimationToHome() { 890 return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); 891 } 892 }; 893 mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, 894 isPresent -> mRecentsView.startHome()); 895 } 896 RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory); 897 windowAnim.addAnimatorListener(new AnimationSuccessListener() { 898 @Override 899 public void onAnimationSuccess(Animator animator) { 900 setStateOnUiThread(target.endState); 901 } 902 }); 903 windowAnim.start(velocityPxPerMs); 904 homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y); 905 mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim); 906 mLauncherTransitionController = null; 907 } else { 908 ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end); 909 windowAnim.setDuration(duration).setInterpolator(interpolator); 910 windowAnim.addUpdateListener(valueAnimator -> { 911 if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) { 912 // Views typically don't compute scroll when invisible as an optimization, 913 // but in our case we need to since the window offset depends on the scroll. 914 mRecentsView.computeScroll(); 915 } 916 }); 917 windowAnim.addListener(new AnimationSuccessListener() { 918 @Override 919 public void onAnimationSuccess(Animator animator) { 920 if (target == NEW_TASK && mRecentsView != null 921 && mRecentsView.getNextPage() == mRecentsView.getRunningTaskIndex()) { 922 // We are about to launch the current running task, so use LAST_TASK state 923 // instead of NEW_TASK. This could happen, for example, if our scroll is 924 // aborted after we determined the target to be NEW_TASK. 925 setStateOnUiThread(LAST_TASK.endState); 926 } else { 927 setStateOnUiThread(target.endState); 928 } 929 } 930 }); 931 windowAnim.start(); 932 mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim); 933 } 934 // Always play the entire launcher animation when going home, since it is separate from 935 // the animation that has been controlled thus far. 936 if (mGestureEndTarget == HOME) { 937 start = 0; 938 } 939 940 // We want to use the same interpolator as the window, but need to adjust it to 941 // interpolate over the remaining progress (end - start). 942 TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress( 943 interpolator, start, end); 944 if (mLauncherTransitionController == null) { 945 return; 946 } 947 if (start == end || duration <= 0) { 948 mLauncherTransitionController.dispatchSetInterpolator(t -> end); 949 } else { 950 mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator); 951 mAnimationFactory.adjustActivityControllerInterpolators(); 952 } 953 mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration)); 954 955 if (QUICKSTEP_SPRINGS.get()) { 956 mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y); 957 } 958 mLauncherTransitionController.getAnimationPlayer().start(); 959 mHasLauncherTransitionControllerStarted = true; 960 } 961 962 /** 963 * Creates an animation that transforms the current app window into the home app. 964 * @param startProgress The progress of {@link #mCurrentShift} to start the window from. 965 * @param homeAnimationFactory The home animation factory. 966 */ 967 @Override createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)968 protected RectFSpringAnim createWindowAnimationToHome(float startProgress, 969 HomeAnimationFactory homeAnimationFactory) { 970 RectFSpringAnim anim = 971 super.createWindowAnimationToHome(startProgress, homeAnimationFactory); 972 anim.addOnUpdateListener((r, p) -> updateSysUiFlags(Math.max(p, mCurrentShift.value))); 973 anim.addAnimatorListener(new AnimationSuccessListener() { 974 @Override 975 public void onAnimationStart(Animator animation) { 976 if (mActivity != null) { 977 removeLiveTileOverlay(); 978 } 979 } 980 981 @Override 982 public void onAnimationSuccess(Animator animator) { 983 if (mRecentsView != null) { 984 mRecentsView.post(mRecentsView::resetTaskVisuals); 985 } 986 // Make sure recents is in its final state 987 maybeUpdateRecentsAttachedState(false); 988 mActivityControlHelper.onSwipeUpToHomeComplete(mActivity); 989 } 990 }); 991 return anim; 992 } 993 994 @Override onConsumerAboutToBeSwitched(SwipeSharedState sharedState)995 public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { 996 if (mGestureEndTarget != null) { 997 sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued; 998 sharedState.goingToLauncher = mGestureEndTarget.isLauncher; 999 } 1000 1001 if (sharedState.canGestureBeContinued) { 1002 cancelCurrentAnimation(sharedState); 1003 } else { 1004 reset(); 1005 } 1006 } 1007 1008 @UiThread resumeLastTask()1009 private void resumeLastTask() { 1010 mRecentsAnimationWrapper.finish(false /* toRecents */, null); 1011 TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false); 1012 doLogGesture(LAST_TASK); 1013 reset(); 1014 } 1015 1016 @UiThread startNewTask()1017 private void startNewTask() { 1018 startNewTask(STATE_HANDLER_INVALIDATED, success -> { 1019 if (!success) { 1020 // We couldn't launch the task, so take user to overview so they can 1021 // decide what to do instead of staying in this broken state. 1022 endLauncherTransitionController(); 1023 updateSysUiFlags(1 /* windowProgress == overview */); 1024 } 1025 doLogGesture(NEW_TASK); 1026 }); 1027 } 1028 reset()1029 private void reset() { 1030 setStateOnUiThread(STATE_HANDLER_INVALIDATED); 1031 } 1032 1033 /** 1034 * Cancels any running animation so that the active target can be overriden by a new swipe 1035 * handle (in case of quick switch). 1036 */ cancelCurrentAnimation(SwipeSharedState sharedState)1037 private void cancelCurrentAnimation(SwipeSharedState sharedState) { 1038 mCanceled = true; 1039 mCurrentShift.cancelAnimation(); 1040 if (mLauncherTransitionController != null && mLauncherTransitionController 1041 .getAnimationPlayer().isStarted()) { 1042 mLauncherTransitionController.getAnimationPlayer().cancel(); 1043 } 1044 1045 if (mFinishingRecentsAnimationForNewTaskId != -1) { 1046 // If we are canceling mid-starting a new task, switch to the screenshot since the 1047 // recents animation has finished 1048 switchToScreenshot(); 1049 TaskView newRunningTaskView = mRecentsView.getTaskView( 1050 mFinishingRecentsAnimationForNewTaskId); 1051 int newRunningTaskId = newRunningTaskView != null 1052 ? newRunningTaskView.getTask().key.id 1053 : -1; 1054 mRecentsView.setCurrentTask(newRunningTaskId); 1055 sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId); 1056 } 1057 } 1058 invalidateHandler()1059 private void invalidateHandler() { 1060 endRunningWindowAnim(false /* cancel */); 1061 1062 if (mGestureEndCallback != null) { 1063 mGestureEndCallback.run(); 1064 } 1065 1066 mActivityInitListener.unregister(); 1067 mTaskSnapshot = null; 1068 } 1069 invalidateHandlerWithLauncher()1070 private void invalidateHandlerWithLauncher() { 1071 endLauncherTransitionController(); 1072 1073 mRecentsView.onGestureAnimationEnd(); 1074 1075 mActivity.getRootView().setOnApplyWindowInsetsListener(null); 1076 removeLiveTileOverlay(); 1077 } 1078 endLauncherTransitionController()1079 private void endLauncherTransitionController() { 1080 setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); 1081 if (mLauncherTransitionController != null) { 1082 mLauncherTransitionController.getAnimationPlayer().end(); 1083 mLauncherTransitionController = null; 1084 } 1085 } 1086 notifyTransitionCancelled()1087 private void notifyTransitionCancelled() { 1088 mAnimationFactory.onTransitionCancelled(); 1089 } 1090 resetStateForAnimationCancel()1091 private void resetStateForAnimationCancel() { 1092 boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted; 1093 mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible); 1094 1095 // Leave the pending invisible flag, as it may be used by wallpaper open animation. 1096 mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER); 1097 } 1098 switchToScreenshot()1099 private void switchToScreenshot() { 1100 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 1101 setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); 1102 } else if (!mRecentsAnimationWrapper.hasTargets()) { 1103 // If there are no targets, then we don't need to capture anything 1104 setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); 1105 } else { 1106 boolean finishTransitionPosted = false; 1107 SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController(); 1108 if (controller != null) { 1109 // Update the screenshot of the task 1110 if (mTaskSnapshot == null) { 1111 mTaskSnapshot = controller.screenshotTask(mRunningTaskId); 1112 } 1113 final TaskView taskView; 1114 if (mGestureEndTarget == HOME) { 1115 // Capture the screenshot before finishing the transition to home to ensure it's 1116 // taken in the correct orientation, but no need to update the thumbnail. 1117 taskView = null; 1118 } else { 1119 taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot); 1120 } 1121 if (taskView != null && !mCanceled) { 1122 // Defer finishing the animation until the next launcher frame with the 1123 // new thumbnail 1124 finishTransitionPosted = new WindowCallbacksCompat(taskView) { 1125 1126 // The number of frames to defer until we actually finish the animation 1127 private int mDeferFrameCount = 2; 1128 1129 @Override 1130 public void onPostDraw(Canvas canvas) { 1131 // If we were cancelled after this was attached, do not update 1132 // the state. 1133 if (mCanceled) { 1134 detach(); 1135 return; 1136 } 1137 1138 if (mDeferFrameCount > 0) { 1139 mDeferFrameCount--; 1140 // Workaround, detach and reattach to invalidate the root node for 1141 // another draw 1142 detach(); 1143 attach(); 1144 taskView.invalidate(); 1145 return; 1146 } 1147 1148 setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); 1149 detach(); 1150 } 1151 }.attach(); 1152 } 1153 } 1154 if (!finishTransitionPosted) { 1155 // If we haven't posted a draw callback, set the state immediately. 1156 RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, ENTER); 1157 setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); 1158 RaceConditionTracker.onEvent(SCREENSHOT_CAPTURED_EVT, EXIT); 1159 } 1160 } 1161 } 1162 finishCurrentTransitionToRecents()1163 private void finishCurrentTransitionToRecents() { 1164 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 1165 setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); 1166 } else if (!mRecentsAnimationWrapper.hasTargets()) { 1167 // If there are no targets, then there is nothing to finish 1168 setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); 1169 } else { 1170 synchronized (mRecentsAnimationWrapper) { 1171 mRecentsAnimationWrapper.finish(true /* toRecents */, 1172 () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); 1173 } 1174 } 1175 TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); 1176 } 1177 finishCurrentTransitionToHome()1178 private void finishCurrentTransitionToHome() { 1179 synchronized (mRecentsAnimationWrapper) { 1180 mRecentsAnimationWrapper.finish(true /* toRecents */, 1181 () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED), 1182 true /* sendUserLeaveHint */); 1183 } 1184 TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); 1185 doLogGesture(HOME); 1186 } 1187 setupLauncherUiAfterSwipeUpToRecentsAnimation()1188 private void setupLauncherUiAfterSwipeUpToRecentsAnimation() { 1189 endLauncherTransitionController(); 1190 mActivityControlHelper.onSwipeUpToRecentsComplete(mActivity); 1191 mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */, 1192 true /* screenshot */); 1193 mRecentsView.onSwipeUpAnimationSuccess(); 1194 1195 RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); 1196 1197 doLogGesture(RECENTS); 1198 reset(); 1199 } 1200 setTargetAlphaProvider(TargetAlphaProvider provider)1201 private void setTargetAlphaProvider(TargetAlphaProvider provider) { 1202 mClipAnimationHelper.setTaskAlphaCallback(provider); 1203 updateFinalShift(); 1204 } 1205 addLiveTileOverlay()1206 private synchronized void addLiveTileOverlay() { 1207 if (!mLiveTileOverlayAttached) { 1208 mActivity.getRootView().getOverlay().add(mLiveTileOverlay); 1209 mRecentsView.setLiveTileOverlay(mLiveTileOverlay); 1210 mLiveTileOverlayAttached = true; 1211 } 1212 } 1213 removeLiveTileOverlay()1214 private synchronized void removeLiveTileOverlay() { 1215 if (mLiveTileOverlayAttached) { 1216 mActivity.getRootView().getOverlay().remove(mLiveTileOverlay); 1217 mRecentsView.setLiveTileOverlay(null); 1218 mLiveTileOverlayAttached = false; 1219 } 1220 } 1221 getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha)1222 public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) { 1223 if (!isNotInRecents(app)) { 1224 return 0; 1225 } 1226 return expectedAlpha; 1227 } 1228 isNotInRecents(RemoteAnimationTargetCompat app)1229 private static boolean isNotInRecents(RemoteAnimationTargetCompat app) { 1230 return app.isNotInRecents 1231 || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; 1232 } 1233 } 1234