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