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 android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 19 import static android.view.Surface.ROTATION_0; 20 import static android.view.Surface.ROTATION_270; 21 import static android.view.Surface.ROTATION_90; 22 import static android.widget.Toast.LENGTH_SHORT; 23 24 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER; 25 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; 26 import static com.android.launcher3.PagedView.INVALID_PAGE; 27 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; 28 import static com.android.launcher3.anim.Interpolators.DEACCEL; 29 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; 30 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; 31 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE; 32 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; 33 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE; 34 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT; 35 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; 36 import static com.android.launcher3.uioverrides.QuickstepLauncher.ENABLE_PIP_KEEP_CLEAR_ALGORITHM; 37 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 38 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 39 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; 40 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; 41 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; 42 import static com.android.quickstep.GestureState.GestureEndTarget.HOME; 43 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; 44 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK; 45 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; 46 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED; 47 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET; 48 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED; 49 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED; 50 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; 51 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION; 52 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED; 53 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED; 54 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET; 55 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; 56 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; 57 58 import android.animation.Animator; 59 import android.animation.AnimatorListenerAdapter; 60 import android.animation.AnimatorSet; 61 import android.animation.ValueAnimator; 62 import android.annotation.TargetApi; 63 import android.app.Activity; 64 import android.app.ActivityManager; 65 import android.app.WindowConfiguration; 66 import android.content.Context; 67 import android.content.Intent; 68 import android.content.res.Resources; 69 import android.graphics.Matrix; 70 import android.graphics.PointF; 71 import android.graphics.Rect; 72 import android.graphics.RectF; 73 import android.os.Build; 74 import android.os.IBinder; 75 import android.os.SystemClock; 76 import android.util.Log; 77 import android.view.MotionEvent; 78 import android.view.RemoteAnimationTarget; 79 import android.view.View; 80 import android.view.View.OnApplyWindowInsetsListener; 81 import android.view.ViewGroup; 82 import android.view.ViewTreeObserver.OnDrawListener; 83 import android.view.ViewTreeObserver.OnScrollChangedListener; 84 import android.view.WindowInsets; 85 import android.view.animation.Interpolator; 86 import android.widget.Toast; 87 import android.window.PictureInPictureSurfaceTransaction; 88 89 import androidx.annotation.Nullable; 90 import androidx.annotation.UiThread; 91 92 import com.android.internal.util.LatencyTracker; 93 import com.android.launcher3.AbstractFloatingView; 94 import com.android.launcher3.DeviceProfile; 95 import com.android.launcher3.R; 96 import com.android.launcher3.Utilities; 97 import com.android.launcher3.anim.AnimationSuccessListener; 98 import com.android.launcher3.anim.AnimatorPlaybackController; 99 import com.android.launcher3.dragndrop.DragView; 100 import com.android.launcher3.logging.StatsLogManager; 101 import com.android.launcher3.logging.StatsLogManager.StatsLogger; 102 import com.android.launcher3.statemanager.BaseState; 103 import com.android.launcher3.statemanager.StatefulActivity; 104 import com.android.launcher3.taskbar.TaskbarUIController; 105 import com.android.launcher3.tracing.InputConsumerProto; 106 import com.android.launcher3.tracing.SwipeHandlerProto; 107 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter; 108 import com.android.launcher3.util.DisplayController; 109 import com.android.launcher3.util.TraceHelper; 110 import com.android.launcher3.util.VibratorWrapper; 111 import com.android.launcher3.util.WindowBounds; 112 import com.android.quickstep.BaseActivityInterface.AnimationFactory; 113 import com.android.quickstep.GestureState.GestureEndTarget; 114 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; 115 import com.android.quickstep.util.ActiveGestureErrorDetector; 116 import com.android.quickstep.util.ActiveGestureLog; 117 import com.android.quickstep.util.ActivityInitListener; 118 import com.android.quickstep.util.AnimatorControllerWithResistance; 119 import com.android.quickstep.util.InputConsumerProxy; 120 import com.android.quickstep.util.InputProxyHandlerFactory; 121 import com.android.quickstep.util.MotionPauseDetector; 122 import com.android.quickstep.util.ProtoTracer; 123 import com.android.quickstep.util.RecentsOrientedState; 124 import com.android.quickstep.util.RectFSpringAnim; 125 import com.android.quickstep.util.StaggeredWorkspaceAnim; 126 import com.android.quickstep.util.SurfaceTransaction; 127 import com.android.quickstep.util.SurfaceTransactionApplier; 128 import com.android.quickstep.util.SwipePipToHomeAnimator; 129 import com.android.quickstep.util.TaskViewSimulator; 130 import com.android.quickstep.views.DesktopTaskView; 131 import com.android.quickstep.views.RecentsView; 132 import com.android.quickstep.views.TaskView; 133 import com.android.systemui.shared.recents.model.Task; 134 import com.android.systemui.shared.recents.model.ThumbnailData; 135 import com.android.systemui.shared.system.ActivityManagerWrapper; 136 import com.android.systemui.shared.system.InputConsumerController; 137 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 138 import com.android.systemui.shared.system.TaskStackChangeListener; 139 import com.android.systemui.shared.system.TaskStackChangeListeners; 140 import com.android.wm.shell.common.TransactionPool; 141 import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils; 142 143 import java.util.ArrayList; 144 import java.util.Arrays; 145 import java.util.HashMap; 146 import java.util.Optional; 147 import java.util.function.Consumer; 148 149 /** 150 * Handles the navigation gestures when Launcher is the default home activity. 151 */ 152 @TargetApi(Build.VERSION_CODES.R) 153 public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, 154 Q extends RecentsView, S extends BaseState<S>> 155 extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener, 156 RecentsAnimationCallbacks.RecentsAnimationListener { 157 private static final String TAG = "AbsSwipeUpHandler"; 158 159 private static final ArrayList<String> STATE_NAMES = new ArrayList<>(); 160 161 protected final BaseActivityInterface<S, T> mActivityInterface; 162 protected final InputConsumerProxy mInputConsumerProxy; 163 protected final ActivityInitListener mActivityInitListener; 164 // Callbacks to be made once the recents animation starts 165 private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>(); 166 private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll; 167 168 // Null if the recents animation hasn't started yet or has been canceled or finished. 169 protected @Nullable RecentsAnimationController mRecentsAnimationController; 170 protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController; 171 protected RecentsAnimationTargets mRecentsAnimationTargets; 172 protected T mActivity; 173 protected Q mRecentsView; 174 protected Runnable mGestureEndCallback; 175 protected MultiStateCallback mStateCallback; 176 protected boolean mCanceled; 177 private boolean mRecentsViewScrollLinked = false; 178 private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks = 179 new ActivityLifecycleCallbacksAdapter() { 180 @Override 181 public void onActivityDestroyed(Activity activity) { 182 if (mActivity != activity) { 183 return; 184 } 185 ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED); 186 mRecentsView = null; 187 mActivity = null; 188 } 189 }; 190 191 private static int FLAG_COUNT = 0; getNextStateFlag(String name)192 private static int getNextStateFlag(String name) { 193 if (DEBUG_STATES) { 194 STATE_NAMES.add(name); 195 } 196 int index = 1 << FLAG_COUNT; 197 FLAG_COUNT++; 198 return index; 199 } 200 201 // Launcher UI related states 202 protected static final int STATE_LAUNCHER_PRESENT = 203 getNextStateFlag("STATE_LAUNCHER_PRESENT"); 204 protected static final int STATE_LAUNCHER_STARTED = 205 getNextStateFlag("STATE_LAUNCHER_STARTED"); 206 protected static final int STATE_LAUNCHER_DRAWN = 207 getNextStateFlag("STATE_LAUNCHER_DRAWN"); 208 // Called when the Launcher has connected to the touch interaction service (and the taskbar 209 // ui controller is initialized) 210 protected static final int STATE_LAUNCHER_BIND_TO_SERVICE = 211 getNextStateFlag("STATE_LAUNCHER_BIND_TO_SERVICE"); 212 213 // Internal initialization states 214 private static final int STATE_APP_CONTROLLER_RECEIVED = 215 getNextStateFlag("STATE_APP_CONTROLLER_RECEIVED"); 216 217 // Interaction finish states 218 private static final int STATE_SCALED_CONTROLLER_HOME = 219 getNextStateFlag("STATE_SCALED_CONTROLLER_HOME"); 220 private static final int STATE_SCALED_CONTROLLER_RECENTS = 221 getNextStateFlag("STATE_SCALED_CONTROLLER_RECENTS"); 222 223 protected static final int STATE_HANDLER_INVALIDATED = 224 getNextStateFlag("STATE_HANDLER_INVALIDATED"); 225 private static final int STATE_GESTURE_STARTED = 226 getNextStateFlag("STATE_GESTURE_STARTED"); 227 private static final int STATE_GESTURE_CANCELLED = 228 getNextStateFlag("STATE_GESTURE_CANCELLED"); 229 private static final int STATE_GESTURE_COMPLETED = 230 getNextStateFlag("STATE_GESTURE_COMPLETED"); 231 232 private static final int STATE_CAPTURE_SCREENSHOT = 233 getNextStateFlag("STATE_CAPTURE_SCREENSHOT"); 234 protected static final int STATE_SCREENSHOT_CAPTURED = 235 getNextStateFlag("STATE_SCREENSHOT_CAPTURED"); 236 private static final int STATE_SCREENSHOT_VIEW_SHOWN = 237 getNextStateFlag("STATE_SCREENSHOT_VIEW_SHOWN"); 238 239 private static final int STATE_RESUME_LAST_TASK = 240 getNextStateFlag("STATE_RESUME_LAST_TASK"); 241 private static final int STATE_START_NEW_TASK = 242 getNextStateFlag("STATE_START_NEW_TASK"); 243 private static final int STATE_CURRENT_TASK_FINISHED = 244 getNextStateFlag("STATE_CURRENT_TASK_FINISHED"); 245 private static final int STATE_FINISH_WITH_NO_END = 246 getNextStateFlag("STATE_FINISH_WITH_NO_END"); 247 248 private static final int LAUNCHER_UI_STATES = 249 STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED | 250 STATE_LAUNCHER_BIND_TO_SERVICE; 251 252 public static final long MAX_SWIPE_DURATION = 350; 253 254 public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f; 255 private static final float SWIPE_DURATION_MULTIPLIER = 256 Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW)); 257 private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured"; 258 259 public static final long RECENTS_ATTACH_DURATION = 300; 260 261 private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f; 262 263 // Controls task thumbnail splash's reveal animation after landing on a task from quickswitch. 264 // These values match WindowManager/Shell starting_window_app_reveal_* config values. 265 private static final int SPLASH_FADE_OUT_DURATION = 133; 266 private static final int SPLASH_APP_REVEAL_DELAY = 83; 267 private static final int SPLASH_APP_REVEAL_DURATION = 266; 268 private static final int SPLASH_ANIMATION_DURATION = 349; 269 270 /** 271 * Used as the page index for logging when we return to the last task at the end of the gesture. 272 */ 273 private static final int LOG_NO_OP_PAGE_INDEX = -1; 274 275 protected final TaskAnimationManager mTaskAnimationManager; 276 277 // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise 278 private RunningWindowAnim[] mRunningWindowAnim; 279 // Possible second animation running at the same time as mRunningWindowAnim 280 private Animator mParallelRunningAnim; 281 private boolean mIsMotionPaused; 282 private boolean mHasMotionEverBeenPaused; 283 284 private boolean mContinuingLastGesture; 285 286 private ThumbnailData mTaskSnapshot; 287 288 // Used to control launcher components throughout the swipe gesture. 289 private AnimatorControllerWithResistance mLauncherTransitionController; 290 private boolean mHasEndedLauncherTransition; 291 292 private AnimationFactory mAnimationFactory = (t) -> { }; 293 294 private boolean mWasLauncherAlreadyVisible; 295 296 private boolean mGestureStarted; 297 private boolean mLogDirectionUpOrLeft = true; 298 private PointF mDownPos; 299 private boolean mIsLikelyToStartNewTask; 300 301 private final long mTouchTimeMs; 302 private long mLauncherFrameDrawnTime; 303 304 private final int mSplashMainWindowShiftLength; 305 306 private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch; 307 308 private SwipePipToHomeAnimator mSwipePipToHomeAnimator; 309 protected boolean mIsSwipingPipToHome; 310 // TODO(b/195473090) no split PIP for now, remove once we have more clarity 311 // can try to have RectFSpringAnim evaluate multiple rects at once 312 private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators = 313 new SwipePipToHomeAnimator[2]; 314 315 // Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold 316 private final float mQuickSwitchScaleScrollThreshold; 317 318 private final int mTaskbarAppWindowThreshold; 319 private final int mTaskbarHomeOverviewThreshold; 320 private final int mTaskbarCatchUpThreshold; 321 private final boolean mTaskbarAlreadyOpen; 322 private final boolean mIsTaskbarAllAppsOpen; 323 private final boolean mIsTransientTaskbar; 324 // May be set to false when mIsTransientTaskbar is true. 325 private boolean mCanSlowSwipeGoHome = true; 326 // Indicates whether the divider is shown, only used when split screen is activated. 327 private boolean mIsDividerShown = true; 328 329 @Nullable 330 private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null; 331 AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, boolean continuingLastGesture, InputConsumerController inputConsumer)332 public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState, 333 TaskAnimationManager taskAnimationManager, GestureState gestureState, 334 long touchTimeMs, boolean continuingLastGesture, 335 InputConsumerController inputConsumer) { 336 super(context, deviceState, gestureState); 337 mActivityInterface = gestureState.getActivityInterface(); 338 mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit); 339 mInputConsumerProxy = 340 new InputConsumerProxy(context, /* rotationSupplier = */ () -> { 341 if (mRecentsView == null) { 342 return ROTATION_0; 343 } 344 return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation(); 345 }, inputConsumer, /* callback = */ () -> { 346 endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */); 347 endLauncherTransitionController(); 348 }, new InputProxyHandlerFactory(mActivityInterface, mGestureState)); 349 mTaskAnimationManager = taskAnimationManager; 350 mTouchTimeMs = touchTimeMs; 351 mContinuingLastGesture = continuingLastGesture; 352 353 Resources res = context.getResources(); 354 mQuickSwitchScaleScrollThreshold = res 355 .getDimension(R.dimen.quick_switch_scaling_scroll_threshold); 356 357 mSplashMainWindowShiftLength = -res 358 .getDimensionPixelSize(R.dimen.starting_surface_exit_animation_window_shift_length); 359 360 initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator() 361 .getOrientationState().getLauncherDeviceProfile()); 362 initStateCallbacks(); 363 364 mIsTransientTaskbar = mDp.isTaskbarPresent 365 && DisplayController.isTransientTaskbar(mActivity); 366 TaskbarUIController controller = mActivityInterface.getTaskbarController(); 367 mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed(); 368 mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen(); 369 mTaskbarAppWindowThreshold = 370 res.getDimensionPixelSize(R.dimen.taskbar_app_window_threshold); 371 boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen; 372 mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar 373 ? 0 374 : res.getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold); 375 mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold); 376 } 377 378 @Nullable getTrackedEventForState(int stateFlag)379 private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { 380 if (stateFlag == STATE_GESTURE_STARTED) { 381 return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_STARTED; 382 } else if (stateFlag == STATE_GESTURE_COMPLETED) { 383 return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_COMPLETED; 384 } else if (stateFlag == STATE_GESTURE_CANCELLED) { 385 return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_CANCELLED; 386 } else if (stateFlag == STATE_SCREENSHOT_CAPTURED) { 387 return ActiveGestureErrorDetector.GestureEvent.STATE_SCREENSHOT_CAPTURED; 388 } else if (stateFlag == STATE_CAPTURE_SCREENSHOT) { 389 return ActiveGestureErrorDetector.GestureEvent.STATE_CAPTURE_SCREENSHOT; 390 } else if (stateFlag == STATE_HANDLER_INVALIDATED) { 391 return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED; 392 } else if (stateFlag == STATE_LAUNCHER_DRAWN) { 393 return ActiveGestureErrorDetector.GestureEvent.STATE_LAUNCHER_DRAWN; 394 } 395 return null; 396 } 397 initStateCallbacks()398 private void initStateCallbacks() { 399 mStateCallback = new MultiStateCallback( 400 STATE_NAMES.toArray(new String[0]), AbsSwipeUpHandler::getTrackedEventForState); 401 402 mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, 403 this::onLauncherPresentAndGestureStarted); 404 405 mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED, 406 this::initializeLauncherAnimationController); 407 408 mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN, 409 this::launcherFrameDrawn); 410 411 mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED 412 | STATE_GESTURE_CANCELLED, 413 this::resetStateForAnimationCancel); 414 415 mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED, 416 this::resumeLastTask); 417 mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED, 418 this::startNewTask); 419 420 mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED 421 | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT, 422 this::switchToScreenshot); 423 424 mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED 425 | STATE_SCALED_CONTROLLER_RECENTS, 426 this::finishCurrentTransitionToRecents); 427 428 mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED 429 | STATE_SCALED_CONTROLLER_HOME, 430 this::finishCurrentTransitionToHome); 431 mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, 432 this::reset); 433 434 mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED 435 | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS 436 | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED 437 | STATE_GESTURE_STARTED, 438 this::setupLauncherUiAfterSwipeUpToRecentsAnimation); 439 440 mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, 441 this::continueComputingRecentsScrollIfNecessary); 442 mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED 443 | STATE_RECENTS_SCROLLING_FINISHED, 444 this::onSettledOnEndTarget); 445 446 mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler); 447 mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, 448 this::invalidateHandlerWithLauncher); 449 mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, 450 this::resetStateForAnimationCancel); 451 mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END, 452 this::resetStateForAnimationCancel); 453 } 454 onActivityInit(Boolean alreadyOnHome)455 protected boolean onActivityInit(Boolean alreadyOnHome) { 456 if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { 457 return false; 458 } 459 460 T createdActivity = mActivityInterface.getCreatedActivity(); 461 if (createdActivity != null) { 462 initTransitionEndpoints(createdActivity.getDeviceProfile()); 463 } 464 final T activity = mActivityInterface.getCreatedActivity(); 465 if (mActivity == activity) { 466 return true; 467 } 468 469 if (mActivity != null) { 470 if (mStateCallback.hasStates(STATE_GESTURE_COMPLETED)) { 471 // If the activity has restarted between setting the page scroll settling callback 472 // and actually receiving the callback, just mark the gesture completed 473 mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED); 474 return true; 475 } 476 477 // The launcher may have been recreated as a result of device rotation. 478 int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES; 479 initStateCallbacks(); 480 mStateCallback.setState(oldState); 481 } 482 mWasLauncherAlreadyVisible = alreadyOnHome; 483 mActivity = activity; 484 // Override the visibility of the activity until the gesture actually starts and we swipe 485 // up, or until we transition home and the home animation is composed 486 if (alreadyOnHome) { 487 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 488 } else { 489 mActivity.addForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 490 } 491 492 mRecentsView = activity.getOverviewPanel(); 493 mRecentsView.setOnPageTransitionEndCallback(null); 494 495 mStateCallback.setState(STATE_LAUNCHER_PRESENT); 496 if (alreadyOnHome) { 497 onLauncherStart(); 498 } else { 499 activity.runOnceOnStart(this::onLauncherStart); 500 } 501 502 // Set up a entire animation lifecycle callback to notify the current recents view when 503 // the animation is canceled 504 mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> { 505 HashMap<Integer, ThumbnailData> snapshots = 506 mGestureState.consumeRecentsAnimationCanceledSnapshot(); 507 if (snapshots != null) { 508 mRecentsView.switchToScreenshot(snapshots, () -> { 509 if (mRecentsAnimationController != null) { 510 mRecentsAnimationController.cleanupScreenshot(); 511 } else if (mDeferredCleanupRecentsAnimationController != null) { 512 mDeferredCleanupRecentsAnimationController.cleanupScreenshot(); 513 mDeferredCleanupRecentsAnimationController = null; 514 } 515 }); 516 mRecentsView.onRecentsAnimationComplete(); 517 } 518 }); 519 520 setupRecentsViewUi(); 521 mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll); 522 activity.runOnBindToTouchInteractionService(this::onLauncherBindToService); 523 524 mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks); 525 return true; 526 } 527 528 /** 529 * Return true if the window should be translated horizontally if the recents view scrolls 530 */ moveWindowWithRecentsScroll()531 protected boolean moveWindowWithRecentsScroll() { 532 return mGestureState.getEndTarget() != HOME; 533 } 534 onLauncherStart()535 private void onLauncherStart() { 536 final T activity = mActivityInterface.getCreatedActivity(); 537 if (mActivity != activity) { 538 return; 539 } 540 if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { 541 return; 542 } 543 // RecentsView never updates the display rotation until swipe-up, force update 544 // RecentsOrientedState before passing to TaskViewSimulator. 545 mRecentsView.updateRecentsRotation(); 546 runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() 547 .setOrientationState(mRecentsView.getPagedViewOrientedState())); 548 549 // If we've already ended the gesture and are going home, don't prepare recents UI, 550 // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL. 551 if (mGestureState.getEndTarget() != HOME) { 552 Runnable initAnimFactory = () -> { 553 mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState, 554 mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated); 555 maybeUpdateRecentsAttachedState(false /* animate */); 556 if (mGestureState.getEndTarget() != null) { 557 // Update the end target in case the gesture ended before we init. 558 mAnimationFactory.setEndTarget(mGestureState.getEndTarget()); 559 } 560 }; 561 if (mWasLauncherAlreadyVisible) { 562 // Launcher is visible, but might be about to stop. Thus, if we prepare recents 563 // now, it might get overridden by moveToRestState() in onStop(). To avoid this, 564 // wait until the next gesture (and possibly launcher) starts. 565 mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory); 566 } else { 567 initAnimFactory.run(); 568 } 569 } 570 AbstractFloatingView.closeAllOpenViewsExcept(activity, mWasLauncherAlreadyVisible, 571 AbstractFloatingView.TYPE_LISTENER); 572 573 if (mWasLauncherAlreadyVisible) { 574 mStateCallback.setState(STATE_LAUNCHER_DRAWN); 575 } else { 576 Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init"); 577 View dragLayer = activity.getDragLayer(); 578 dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() { 579 boolean mHandled = false; 580 581 @Override 582 public void onDraw() { 583 if (mHandled) { 584 return; 585 } 586 mHandled = true; 587 588 TraceHelper.INSTANCE.endSection(traceToken); 589 dragLayer.post(() -> 590 dragLayer.getViewTreeObserver().removeOnDrawListener(this)); 591 if (activity != mActivity) { 592 return; 593 } 594 595 mStateCallback.setState(STATE_LAUNCHER_DRAWN); 596 } 597 }); 598 } 599 600 activity.getRootView().setOnApplyWindowInsetsListener(this); 601 mStateCallback.setState(STATE_LAUNCHER_STARTED); 602 } 603 onLauncherBindToService()604 private void onLauncherBindToService() { 605 mStateCallback.setState(STATE_LAUNCHER_BIND_TO_SERVICE); 606 flushOnRecentsAnimationAndLauncherBound(); 607 } 608 onLauncherPresentAndGestureStarted()609 private void onLauncherPresentAndGestureStarted() { 610 // Re-setup the recents UI when gesture starts, as the state could have been changed during 611 // that time by a previous window transition. 612 setupRecentsViewUi(); 613 614 // For the duration of the gesture, in cases where an activity is launched while the 615 // activity is not yet resumed, finish the animation to ensure we get resumed 616 mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback( 617 mOnDeferredActivityLaunch); 618 619 mGestureState.runOnceAtState(STATE_END_TARGET_SET, 620 () -> { 621 mDeviceState.getRotationTouchHelper() 622 .onEndTargetCalculated(mGestureState.getEndTarget(), 623 mActivityInterface); 624 }); 625 626 notifyGestureStartedAsync(); 627 } 628 onDeferredActivityLaunch()629 private void onDeferredActivityLaunch() { 630 mActivityInterface.switchRunningTaskViewToScreenshot( 631 null, () -> { 632 mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); 633 }); 634 } 635 setupRecentsViewUi()636 private void setupRecentsViewUi() { 637 if (mContinuingLastGesture) { 638 updateSysUiFlags(mCurrentShift.value); 639 return; 640 } 641 notifyGestureAnimationStartToRecents(); 642 } 643 notifyGestureAnimationStartToRecents()644 protected void notifyGestureAnimationStartToRecents() { 645 Task[] runningTasks; 646 if (mIsSwipeForSplit) { 647 int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds(); 648 runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds); 649 } else { 650 runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(); 651 } 652 mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper()); 653 } 654 launcherFrameDrawn()655 private void launcherFrameDrawn() { 656 mLauncherFrameDrawnTime = SystemClock.uptimeMillis(); 657 } 658 initializeLauncherAnimationController()659 private void initializeLauncherAnimationController() { 660 buildAnimationController(); 661 662 Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents", 663 TraceHelper.FLAG_IGNORE_BINDERS); 664 LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS, 665 (int) (mLauncherFrameDrawnTime - mTouchTimeMs)); 666 TraceHelper.INSTANCE.endSection(traceToken); 667 668 // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the 669 // high-res thumbnail loader here once we are sure that we will end up in an overview state 670 RecentsModel.INSTANCE.get(mContext).getThumbnailCache() 671 .getHighResLoadingState().setVisible(true); 672 } 673 getMotionPauseListener()674 public MotionPauseDetector.OnMotionPauseListener getMotionPauseListener() { 675 return new MotionPauseDetector.OnMotionPauseListener() { 676 @Override 677 public void onMotionPauseDetected() { 678 mHasMotionEverBeenPaused = true; 679 maybeUpdateRecentsAttachedState(true/* animate */, true/* moveRunningTask */); 680 Optional.ofNullable(mActivityInterface.getTaskbarController()) 681 .ifPresent(TaskbarUIController::startTranslationSpring); 682 performHapticFeedback(); 683 } 684 685 @Override 686 public void onMotionPauseChanged(boolean isPaused) { 687 mIsMotionPaused = isPaused; 688 } 689 }; 690 } 691 692 private void maybeUpdateRecentsAttachedState() { 693 maybeUpdateRecentsAttachedState(true /* animate */); 694 } 695 696 private void maybeUpdateRecentsAttachedState(boolean animate) { 697 maybeUpdateRecentsAttachedState(animate, false /* moveRunningTask */); 698 } 699 700 /** 701 * Determines whether to show or hide RecentsView. The window is always 702 * synchronized with its corresponding TaskView in RecentsView, so if 703 * RecentsView is shown, it will appear to be attached to the window. 704 * 705 * Note this method has no effect unless the navigation mode is NO_BUTTON. 706 * @param animate whether to animate when attaching RecentsView 707 * @param moveRunningTask whether to move running task to front when attaching 708 */ 709 private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) { 710 if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) { 711 return; 712 } 713 RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null 714 ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId()) 715 : null; 716 final boolean recentsAttachedToAppWindow; 717 if (mGestureState.getEndTarget() != null) { 718 recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow; 719 } else if (mContinuingLastGesture 720 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) { 721 recentsAttachedToAppWindow = true; 722 } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) { 723 // The window is going away so make sure recents is always visible in this case. 724 recentsAttachedToAppWindow = true; 725 } else { 726 recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask; 727 } 728 if (moveRunningTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow() 729 && recentsAttachedToAppWindow) { 730 // Only move running task if RecentsView has never been attached before, to avoid 731 // TaskView jumping to new position as we move the tasks. 732 mRecentsView.moveRunningTaskToFront(); 733 } 734 mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate); 735 736 // Reapply window transform throughout the attach animation, as the animation affects how 737 // much the window is bound by overscroll (vs moving freely). 738 if (animate) { 739 ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1); 740 reapplyWindowTransformAnim.addUpdateListener(anim -> { 741 if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) { 742 applyScrollAndTransform(); 743 } 744 }); 745 reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start(); 746 mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, 747 reapplyWindowTransformAnim::cancel); 748 } else { 749 applyScrollAndTransform(); 750 } 751 } 752 753 /** 754 * Returns threshold that needs to be met in order for motion pause to be allowed. 755 */ 756 public float getThresholdToAllowMotionPause() { 757 return mIsTransientTaskbar 758 ? mTaskbarHomeOverviewThreshold 759 : 0; 760 } 761 762 public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { 763 setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */); 764 } 765 766 private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) { 767 if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) { 768 mIsLikelyToStartNewTask = isLikelyToStartNewTask; 769 maybeUpdateRecentsAttachedState(animate); 770 } 771 } 772 773 private void buildAnimationController() { 774 if (!canCreateNewOrUpdateExistingLauncherTransitionController()) { 775 return; 776 } 777 initTransitionEndpoints(mActivity.getDeviceProfile()); 778 mAnimationFactory.createActivityInterface(mTransitionDragLength); 779 } 780 781 /** 782 * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME 783 * (it has its own animation) or if we explicitly ended the controller already. 784 * @return Whether we can create the launcher controller or update its progress. 785 */ 786 private boolean canCreateNewOrUpdateExistingLauncherTransitionController() { 787 return mGestureState.getEndTarget() != HOME && !mHasEndedLauncherTransition; 788 } 789 790 @Override 791 public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) { 792 WindowInsets result = view.onApplyWindowInsets(windowInsets); 793 buildAnimationController(); 794 // Reapply the current shift to ensure it takes new insets into account, e.g. when long 795 // pressing to stash taskbar without moving the finger. 796 updateFinalShift(); 797 return result; 798 } 799 800 private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) { 801 boolean isFirstCreation = mLauncherTransitionController == null; 802 mLauncherTransitionController = anim; 803 if (isFirstCreation) { 804 mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> { 805 // Wait until the gesture is started (touch slop was passed) to start in sync with 806 // mWindowTransitionController. This ensures we don't hide the taskbar background 807 // when long pressing to stash it, for instance. 808 mLauncherTransitionController.getNormalController().dispatchOnStart(); 809 updateLauncherTransitionProgress(); 810 }); 811 } 812 } 813 814 public Intent getLaunchIntent() { 815 return mGestureState.getOverviewIntent(); 816 } 817 818 /** 819 * Called when the value of {@link #mCurrentShift} changes 820 */ 821 @UiThread 822 @Override 823 public void updateFinalShift() { 824 updateSysUiFlags(mCurrentShift.value); 825 applyScrollAndTransform(); 826 827 updateLauncherTransitionProgress(); 828 } 829 830 private void updateLauncherTransitionProgress() { 831 if (mLauncherTransitionController == null 832 || !canCreateNewOrUpdateExistingLauncherTransitionController()) { 833 return; 834 } 835 mLauncherTransitionController.setProgress( 836 Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor); 837 } 838 839 /** 840 * @param windowProgress 0 == app, 1 == overview 841 */ 842 private void updateSysUiFlags(float windowProgress) { 843 if (mRecentsAnimationController != null && mRecentsView != null) { 844 TaskView runningTask = mRecentsView.getRunningTaskView(); 845 TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen(); 846 int centermostTaskFlags = centermostTask == null ? 0 847 : centermostTask.getThumbnail().getSysUiStatusNavFlags(); 848 boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD; 849 boolean quickswitchThresholdPassed = centermostTask != runningTask; 850 851 // We will handle the sysui flags based on the centermost task view. 852 mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed 853 || (quickswitchThresholdPassed && centermostTaskFlags != 0)); 854 mRecentsAnimationController.setSplitScreenMinimized(mContext, swipeUpThresholdPassed); 855 // Provide a hint to WM the direction that we will be settling in case the animation 856 // needs to be canceled 857 mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed); 858 859 if (swipeUpThresholdPassed) { 860 mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0); 861 } else { 862 mActivity.getSystemUiController().updateUiState( 863 UI_STATE_FULLSCREEN_TASK, centermostTaskFlags); 864 } 865 } 866 } 867 868 @Override 869 public void onRecentsAnimationStart(RecentsAnimationController controller, 870 RecentsAnimationTargets targets) { 871 super.onRecentsAnimationStart(controller, targets); 872 if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) { 873 mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets); 874 } else { 875 mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets); 876 } 877 mRecentsAnimationController = controller; 878 mRecentsAnimationTargets = targets; 879 mSwipePipToHomeReleaseCheck = new RemoteAnimationTargets.ReleaseCheck(); 880 mSwipePipToHomeReleaseCheck.setCanRelease(true); 881 mRecentsAnimationTargets.addReleaseCheck(mSwipePipToHomeReleaseCheck); 882 883 // Only initialize the device profile, if it has not been initialized before, as in some 884 // configurations targets.homeContentInsets may not be correct. 885 if (mActivity == null) { 886 RemoteAnimationTarget primaryTaskTarget = targets.apps[0]; 887 // orientation state is independent of which remote target handle we use since both 888 // should be pointing to the same one. Just choose index 0 for now since that works for 889 // both split and non-split 890 RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator() 891 .getOrientationState(); 892 DeviceProfile dp = orientationState.getLauncherDeviceProfile(); 893 if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) { 894 Rect overviewStackBounds = mActivityInterface 895 .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget); 896 dp = dp.getMultiWindowProfile(mContext, 897 new WindowBounds(overviewStackBounds, targets.homeContentInsets)); 898 } else { 899 // If we are not in multi-window mode, home insets should be same as system insets. 900 dp = dp.copy(mContext); 901 } 902 dp.updateInsets(targets.homeContentInsets); 903 dp.updateIsSeascape(mContext); 904 initTransitionEndpoints(dp); 905 orientationState.setMultiWindowMode(dp.isMultiWindowMode); 906 } 907 908 // Notify when the animation starts 909 flushOnRecentsAnimationAndLauncherBound(); 910 911 // Only add the callback to enable the input consumer after we actually have the controller 912 mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, 913 this::startInterceptingTouchesForGesture); 914 mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); 915 } 916 917 @Override 918 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 919 ActiveGestureLog.INSTANCE.addLog( 920 /* event= */ "cancelRecentsAnimation", 921 /* gestureEvent= */ CANCEL_RECENTS_ANIMATION); 922 mActivityInitListener.unregister(); 923 // Cache the recents animation controller so we can defer its cleanup to after having 924 // properly cleaned up the screenshot without accidentally using it. 925 mDeferredCleanupRecentsAnimationController = mRecentsAnimationController; 926 mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); 927 // Defer clearing the controller and the targets until after we've updated the state 928 mRecentsAnimationController = null; 929 mRecentsAnimationTargets = null; 930 if (mRecentsView != null) { 931 mRecentsView.setRecentsAnimationTargets(null, null); 932 } 933 } 934 935 @UiThread 936 public void onGestureStarted(boolean isLikelyToStartNewTask) { 937 mActivityInterface.closeOverlay(); 938 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 939 940 if (mRecentsView != null) { 941 final View rv = mRecentsView; 942 mRecentsView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() { 943 boolean mHandled = false; 944 945 @Override 946 public void onDraw() { 947 if (mHandled) { 948 return; 949 } 950 mHandled = true; 951 952 InteractionJankMonitorWrapper.begin(mRecentsView, 953 InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */); 954 InteractionJankMonitorWrapper.begin(mRecentsView, 955 InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME); 956 InteractionJankMonitorWrapper.begin(mRecentsView, 957 InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS); 958 959 rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this)); 960 } 961 }); 962 } 963 notifyGestureStartedAsync(); 964 setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */); 965 966 if (mIsTransientTaskbar && !mTaskbarAlreadyOpen && !isLikelyToStartNewTask) { 967 setClampScrollOffset(true); 968 } 969 mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); 970 mGestureStarted = true; 971 } 972 973 /** 974 * Sets whether or not we should clamp the scroll offset. 975 * This is used to avoid x-axis movement when swiping up transient taskbar. 976 * @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is 977 * met. 978 */ 979 private void setClampScrollOffset(boolean clampScrollOffset) { 980 if (!mIsTransientTaskbar) { 981 return; 982 } 983 if (mRecentsView == null) { 984 mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT, 985 () -> mRecentsView.setClampScrollOffset(clampScrollOffset)); 986 return; 987 } 988 mRecentsView.setClampScrollOffset(clampScrollOffset); 989 } 990 991 992 /** 993 * Notifies the launcher that the swipe gesture has started. This can be called multiple times. 994 */ 995 @UiThread 996 private void notifyGestureStartedAsync() { 997 final T curActivity = mActivity; 998 if (curActivity != null) { 999 // Once the gesture starts, we can no longer transition home through the button, so 1000 // reset the force override of the activity visibility 1001 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 1002 } 1003 } 1004 1005 /** 1006 * Called as a result on ACTION_CANCEL to return the UI to the start state. 1007 */ 1008 @UiThread 1009 public void onGestureCancelled() { 1010 updateDisplacement(0); 1011 mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); 1012 handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */); 1013 } 1014 1015 /** 1016 * @param endVelocityPxPerMs The velocity in the direction of the nav bar to the middle of the 1017 * screen. 1018 * @param velocityPxPerMs The x and y components of the velocity when the gesture ends. 1019 * @param downPos The x and y value of where the gesture started. 1020 */ 1021 @UiThread 1022 public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs, PointF downPos) { 1023 float flingThreshold = mContext.getResources() 1024 .getDimension(R.dimen.quickstep_fling_threshold_speed); 1025 boolean isFling = mGestureStarted && !mIsMotionPaused 1026 && Math.abs(endVelocityPxPerMs) > flingThreshold; 1027 mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); 1028 boolean isVelocityVertical = Math.abs(velocityPxPerMs.y) > Math.abs(velocityPxPerMs.x); 1029 if (isVelocityVertical) { 1030 mLogDirectionUpOrLeft = velocityPxPerMs.y < 0; 1031 } else { 1032 mLogDirectionUpOrLeft = velocityPxPerMs.x < 0; 1033 } 1034 mDownPos = downPos; 1035 Runnable handleNormalGestureEndCallback = () -> handleNormalGestureEnd( 1036 endVelocityPxPerMs, isFling, velocityPxPerMs, /* isCancel= */ false); 1037 if (mRecentsView != null) { 1038 mRecentsView.runOnPageScrollsInitialized(handleNormalGestureEndCallback); 1039 } else { 1040 handleNormalGestureEndCallback.run(); 1041 } 1042 } 1043 1044 private void endRunningWindowAnim(boolean cancel) { 1045 if (mRunningWindowAnim != null) { 1046 if (cancel) { 1047 for (RunningWindowAnim r : mRunningWindowAnim) { 1048 if (r != null) { 1049 r.cancel(); 1050 } 1051 } 1052 } else { 1053 for (RunningWindowAnim r : mRunningWindowAnim) { 1054 if (r != null) { 1055 r.end(); 1056 } 1057 } 1058 } 1059 } 1060 if (mParallelRunningAnim != null) { 1061 // Unlike the above animation, the parallel animation won't have anything to take up 1062 // the work if it's canceled, so just end it instead. 1063 mParallelRunningAnim.end(); 1064 } 1065 } 1066 1067 private void onSettledOnEndTarget() { 1068 // Fast-finish the attaching animation if it's still running. 1069 maybeUpdateRecentsAttachedState(false); 1070 final GestureEndTarget endTarget = mGestureState.getEndTarget(); 1071 // Wait until the given View (if supplied) draws before resuming the last task. 1072 View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget); 1073 1074 if (endTarget != NEW_TASK) { 1075 InteractionJankMonitorWrapper.cancel( 1076 InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH); 1077 } 1078 if (endTarget != HOME) { 1079 InteractionJankMonitorWrapper.cancel( 1080 InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME); 1081 } 1082 if (endTarget != RECENTS) { 1083 InteractionJankMonitorWrapper.cancel( 1084 InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS); 1085 } 1086 1087 switch (endTarget) { 1088 case HOME: 1089 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT); 1090 // Notify the SysUI to use fade-in animation when entering PiP 1091 SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha(); 1092 break; 1093 case RECENTS: 1094 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT 1095 | STATE_SCREENSHOT_VIEW_SHOWN); 1096 break; 1097 case NEW_TASK: 1098 mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT); 1099 break; 1100 case LAST_TASK: 1101 if (postResumeLastTask != null) { 1102 ViewUtils.postFrameDrawn(postResumeLastTask, 1103 () -> mStateCallback.setState(STATE_RESUME_LAST_TASK)); 1104 } else { 1105 mStateCallback.setState(STATE_RESUME_LAST_TASK); 1106 } 1107 // Restore the divider as it resumes the last top-tasks. 1108 setDividerShown(true); 1109 break; 1110 } 1111 ActiveGestureLog.INSTANCE.addLog( 1112 /* event= */ "onSettledOnEndTarget " + endTarget, 1113 /* gestureEvent= */ ON_SETTLED_ON_END_TARGET); 1114 } 1115 1116 /** @return Whether this was the task we were waiting to appear, and thus handled it. */ 1117 protected boolean handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget) { 1118 if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { 1119 return false; 1120 } 1121 boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch( 1122 targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId()); 1123 if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) { 1124 reset(); 1125 return true; 1126 } 1127 return false; 1128 } 1129 1130 private float dpiFromPx(float pixels) { 1131 return Utilities.dpiFromPx(pixels, mContext.getResources().getDisplayMetrics().densityDpi); 1132 } 1133 1134 private GestureEndTarget calculateEndTarget( 1135 PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) { 1136 ActiveGestureLog.INSTANCE.addLog( 1137 new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=") 1138 .append(Float.toString(dpiFromPx(velocityPxPerMs.x))) 1139 .append("dp/ms, y=") 1140 .append(Float.toString(dpiFromPx(velocityPxPerMs.y))) 1141 .append("dp/ms), angle=") 1142 .append(Double.toString(Math.toDegrees(Math.atan2( 1143 -velocityPxPerMs.y, velocityPxPerMs.x))))); 1144 1145 if (mGestureState.isHandlingAtomicEvent()) { 1146 // Button mode, this is only used to go to recents. 1147 return RECENTS; 1148 } 1149 1150 GestureEndTarget endTarget; 1151 if (isCancel) { 1152 endTarget = LAST_TASK; 1153 } else if (isFlingY) { 1154 endTarget = calculateEndTargetForFlingY(velocityPxPerMs, endVelocityPxPerMs); 1155 } else { 1156 endTarget = calculateEndTargetForNonFling(velocityPxPerMs); 1157 } 1158 1159 if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) { 1160 return LAST_TASK; 1161 } 1162 1163 if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && endTarget == NEW_TASK) { 1164 // TODO(b/268075592): add support for quickswitch to/from desktop 1165 return LAST_TASK; 1166 } 1167 1168 return endTarget; 1169 } 1170 1171 private GestureEndTarget calculateEndTargetForFlingY(PointF velocity, float endVelocity) { 1172 // If swiping at a diagonal, base end target on the faster velocity direction. 1173 final boolean willGoToNewTask = 1174 isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity); 1175 final boolean isSwipeUp = endVelocity < 0; 1176 if (!isSwipeUp) { 1177 final boolean isCenteredOnNewTask = 1178 mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex(); 1179 return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK; 1180 } 1181 1182 return willGoToNewTask ? NEW_TASK : HOME; 1183 } 1184 1185 private GestureEndTarget calculateEndTargetForNonFling(PointF velocity) { 1186 final boolean isScrollingToNewTask = isScrollingToNewTask(); 1187 1188 // Fully gestural mode. 1189 final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources() 1190 .getDimension(R.dimen.quickstep_fling_threshold_speed); 1191 if (isScrollingToNewTask && isFlingX) { 1192 // Flinging towards new task takes precedence over mIsMotionPaused (which only 1193 // checks y-velocity). 1194 return NEW_TASK; 1195 } else if (mIsMotionPaused) { 1196 return RECENTS; 1197 } else if (isScrollingToNewTask) { 1198 return NEW_TASK; 1199 } 1200 return velocity.y < 0 && mCanSlowSwipeGoHome ? HOME : LAST_TASK; 1201 } 1202 1203 private boolean isScrollingToNewTask() { 1204 if (mRecentsView == null) { 1205 return false; 1206 } 1207 if (!hasTargets()) { 1208 // If there are no running tasks, then we can assume that this is a continuation of 1209 // the last gesture, but after the recents animation has finished. 1210 return true; 1211 } 1212 int runningTaskIndex = mRecentsView.getRunningTaskIndex(); 1213 return runningTaskIndex >= 0 && mRecentsView.getNextPage() != runningTaskIndex; 1214 } 1215 1216 /** 1217 * Sets whether a slow swipe can go to the HOME end target when the user lets go. A slow swipe 1218 * for this purpose must meet two criteria: 1219 * 1) y-velocity is less than quickstep_fling_threshold_speed 1220 * AND 1221 * 2) motion pause has not been detected (possibly because 1222 * {@link MotionPauseDetector#setDisallowPause} has been called with disallowPause == true) 1223 */ 1224 public void setCanSlowSwipeGoHome(boolean canSlowSwipeGoHome) { 1225 mCanSlowSwipeGoHome = canSlowSwipeGoHome; 1226 } 1227 1228 @UiThread 1229 private void handleNormalGestureEnd( 1230 float endVelocityPxPerMs, boolean isFling, PointF velocityPxPerMs, boolean isCancel) { 1231 long duration = MAX_SWIPE_DURATION; 1232 float currentShift = mCurrentShift.value; 1233 final GestureEndTarget endTarget = calculateEndTarget( 1234 velocityPxPerMs, endVelocityPxPerMs, isFling, isCancel); 1235 // Set the state, but don't notify until the animation completes 1236 mGestureState.setEndTarget(endTarget, false /* isAtomic */); 1237 mAnimationFactory.setEndTarget(endTarget); 1238 1239 float endShift = endTarget.isLauncher ? 1 : 0; 1240 final float startShift; 1241 if (!isFling) { 1242 long expectedDuration = Math.abs(Math.round((endShift - currentShift) 1243 * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); 1244 duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); 1245 startShift = currentShift; 1246 } else { 1247 startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y 1248 * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); 1249 if (mTransitionDragLength > 0) { 1250 float distanceToTravel = (endShift - currentShift) * mTransitionDragLength; 1251 1252 // we want the page's snap velocity to approximately match the velocity at 1253 // which the user flings, so we scale the duration by a value near to the 1254 // derivative of the scroll interpolator at zero, ie. 2. 1255 long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); 1256 duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); 1257 } 1258 } 1259 Interpolator interpolator; 1260 S state = mActivityInterface.stateFromGestureEndTarget(endTarget); 1261 if (state.displayOverviewTasksAsGrid(mDp)) { 1262 interpolator = ACCEL_DEACCEL; 1263 } else if (endTarget == RECENTS) { 1264 interpolator = OVERSHOOT_1_2; 1265 } else { 1266 interpolator = DEACCEL; 1267 } 1268 1269 if (endTarget.isLauncher) { 1270 mInputConsumerProxy.enable(); 1271 } 1272 if (endTarget == HOME) { 1273 duration = mActivity != null && mActivity.getDeviceProfile().isTaskbarPresent 1274 ? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS 1275 : StaggeredWorkspaceAnim.DURATION_MS; 1276 // Early detach the nav bar once the endTarget is determined as HOME 1277 if (mRecentsAnimationController != null) { 1278 mRecentsAnimationController.detachNavigationBarFromApp(true); 1279 } 1280 } else if (endTarget == RECENTS) { 1281 if (mRecentsView != null) { 1282 int nearestPage = mRecentsView.getDestinationPage(); 1283 if (nearestPage == INVALID_PAGE) { 1284 // Allow the snap to invalid page to catch future error cases. 1285 Log.e(TAG, 1286 "RecentsView destination page is invalid", 1287 new IllegalStateException()); 1288 } 1289 1290 boolean isScrolling = false; 1291 if (mRecentsView.getNextPage() != nearestPage) { 1292 // We shouldn't really scroll to the next page when swiping up to recents. 1293 // Only allow settling on the next page if it's nearest to the center. 1294 mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration)); 1295 isScrolling = true; 1296 } 1297 if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) { 1298 mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION); 1299 isScrolling = true; 1300 } 1301 if (!mGestureState.isHandlingAtomicEvent() || isScrolling) { 1302 duration = Math.max(duration, mRecentsView.getScroller().getDuration()); 1303 } 1304 } 1305 } else if (endTarget == LAST_TASK && mRecentsView != null 1306 && mRecentsView.getNextPage() != mRecentsView.getRunningTaskIndex()) { 1307 mRecentsView.snapToPage(mRecentsView.getRunningTaskIndex(), Math.toIntExact(duration)); 1308 } 1309 1310 // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() 1311 // or resumeLastTask(). 1312 Runnable onPageTransitionEnd = () -> { 1313 mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED); 1314 setClampScrollOffset(false); 1315 }; 1316 if (mRecentsView != null) { 1317 ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent 1318 .SET_ON_PAGE_TRANSITION_END_CALLBACK); 1319 mRecentsView.setOnPageTransitionEndCallback(onPageTransitionEnd); 1320 } else { 1321 onPageTransitionEnd.run(); 1322 } 1323 1324 animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs); 1325 } 1326 1327 private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) { 1328 if (mDp == null || !mDp.isGestureMode || mDownPos == null) { 1329 // We probably never received an animation controller, skip logging. 1330 return; 1331 } 1332 1333 StatsLogManager.EventEnum event; 1334 switch (endTarget) { 1335 case HOME: 1336 event = LAUNCHER_HOME_GESTURE; 1337 break; 1338 case RECENTS: 1339 event = LAUNCHER_OVERVIEW_GESTURE; 1340 break; 1341 case LAST_TASK: 1342 case NEW_TASK: 1343 event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT 1344 : LAUNCHER_QUICKSWITCH_RIGHT; 1345 break; 1346 default: 1347 event = IGNORE; 1348 } 1349 StatsLogger logger = StatsLogManager.newInstance(mContext).logger() 1350 .withSrcState(LAUNCHER_STATE_BACKGROUND) 1351 .withDstState(endTarget.containerType); 1352 if (targetTask != null) { 1353 logger.withItemInfo(targetTask.getItemInfo()); 1354 } 1355 1356 int pageIndex = endTarget == LAST_TASK || mRecentsView == null 1357 ? LOG_NO_OP_PAGE_INDEX 1358 : mRecentsView.getNextPage(); 1359 logger.withRank(pageIndex); 1360 logger.log(event); 1361 } 1362 1363 /** Animates to the given progress, where 0 is the current app and 1 is overview. */ 1364 @UiThread 1365 private void animateToProgress(float start, float end, long duration, Interpolator interpolator, 1366 GestureEndTarget target, PointF velocityPxPerMs) { 1367 runOnRecentsAnimationAndLauncherBound(() -> animateToProgressInternal(start, end, duration, 1368 interpolator, target, velocityPxPerMs)); 1369 } 1370 1371 protected abstract HomeAnimationFactory createHomeAnimationFactory( 1372 ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent, 1373 boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget); 1374 1375 private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() { 1376 @Override 1377 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 1378 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 1379 if (task.taskId == mGestureState.getRunningTaskId() 1380 && task.configuration.windowConfiguration.getActivityType() 1381 != ACTIVITY_TYPE_HOME) { 1382 // Since this is an edge case, just cancel and relaunch with default activity 1383 // options (since we don't know if there's an associated app icon to launch from) 1384 endRunningWindowAnim(true /* cancel */); 1385 TaskStackChangeListeners.getInstance().unregisterTaskStackListener( 1386 mActivityRestartListener); 1387 ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null); 1388 } 1389 } 1390 }; 1391 1392 @UiThread 1393 private void animateToProgressInternal(float start, float end, long duration, 1394 Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) { 1395 maybeUpdateRecentsAttachedState(); 1396 1397 // If we are transitioning to launcher, then listen for the activity to be restarted while 1398 // the transition is in progress 1399 if (mGestureState.getEndTarget().isLauncher) { 1400 // This is also called when the launcher is resumed, in order to clear the pending 1401 // widgets that have yet to be configured. 1402 DragView.removeAllViews(mActivity); 1403 1404 TaskStackChangeListeners.getInstance().registerTaskStackListener( 1405 mActivityRestartListener); 1406 1407 mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher( 1408 mGestureState.getEndTarget(), duration, 1409 mTaskAnimationManager.getCurrentCallbacks()); 1410 if (mParallelRunningAnim != null) { 1411 mParallelRunningAnim.addListener(new AnimatorListenerAdapter() { 1412 @Override 1413 public void onAnimationEnd(Animator animation) { 1414 mParallelRunningAnim = null; 1415 } 1416 }); 1417 mParallelRunningAnim.start(); 1418 } 1419 } 1420 1421 if (mGestureState.getEndTarget() == HOME) { 1422 getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs); 1423 final RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null 1424 ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId()) 1425 : null; 1426 final ArrayList<IBinder> cookies = runningTaskTarget != null 1427 ? runningTaskTarget.taskInfo.launchCookies 1428 : new ArrayList<>(); 1429 boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent; 1430 boolean hasValidLeash = runningTaskTarget != null 1431 && runningTaskTarget.leash != null 1432 && runningTaskTarget.leash.isValid(); 1433 boolean appCanEnterPip = !mDeviceState.isPipActive() 1434 && hasValidLeash 1435 && runningTaskTarget.allowEnterPip 1436 && runningTaskTarget.taskInfo.pictureInPictureParams != null 1437 && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled(); 1438 HomeAnimationFactory homeAnimFactory = 1439 createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip, 1440 runningTaskTarget); 1441 mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip; 1442 final RectFSpringAnim[] windowAnim; 1443 if (mIsSwipingPipToHome) { 1444 mSwipePipToHomeAnimator = createWindowAnimationToPip( 1445 homeAnimFactory, runningTaskTarget, start); 1446 mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator; 1447 if (mSwipePipToHomeReleaseCheck != null) { 1448 mSwipePipToHomeReleaseCheck.setCanRelease(false); 1449 } 1450 windowAnim = mSwipePipToHomeAnimators; 1451 } else { 1452 mSwipePipToHomeAnimator = null; 1453 if (mSwipePipToHomeReleaseCheck != null) { 1454 mSwipePipToHomeReleaseCheck.setCanRelease(true); 1455 mSwipePipToHomeReleaseCheck = null; 1456 } 1457 windowAnim = createWindowAnimationToHome(start, homeAnimFactory); 1458 1459 windowAnim[0].addAnimatorListener(new AnimationSuccessListener() { 1460 @Override 1461 public void onAnimationSuccess(Animator animator) { 1462 if (mRecentsAnimationController == null) { 1463 // If the recents animation is interrupted, we still end the running 1464 // animation (not canceled) so this is still called. In that case, 1465 // we can skip doing any future work here for the current gesture. 1466 return; 1467 } 1468 // Finalize the state and notify of the change 1469 mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); 1470 } 1471 }); 1472 } 1473 mRunningWindowAnim = new RunningWindowAnim[windowAnim.length]; 1474 for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) { 1475 RectFSpringAnim windowAnimation = windowAnim[i]; 1476 if (windowAnimation == null) { 1477 continue; 1478 } 1479 DeviceProfile dp = mActivity == null ? null : mActivity.getDeviceProfile(); 1480 windowAnimation.start(mContext, dp, velocityPxPerMs); 1481 mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation); 1482 } 1483 homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y); 1484 homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y); 1485 mLauncherTransitionController = null; 1486 1487 if (mRecentsView != null) { 1488 mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(), 1489 getRemoteTaskViewSimulators()); 1490 } 1491 } else { 1492 AnimatorSet animatorSet = new AnimatorSet(); 1493 ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end); 1494 windowAnim.addUpdateListener(valueAnimator -> { 1495 computeRecentsScrollIfInvisible(); 1496 }); 1497 windowAnim.addListener(new AnimationSuccessListener() { 1498 @Override 1499 public void onAnimationSuccess(Animator animator) { 1500 if (mRecentsAnimationController == null) { 1501 // If the recents animation is interrupted, we still end the running 1502 // animation (not canceled) so this is still called. In that case, we can 1503 // skip doing any future work here for the current gesture. 1504 return; 1505 } 1506 if (mRecentsView != null) { 1507 int taskToLaunch = mRecentsView.getNextPage(); 1508 int runningTask = getLastAppearedTaskIndex(); 1509 boolean hasStartedNewTask = hasStartedNewTask(); 1510 if (target == NEW_TASK && taskToLaunch == runningTask 1511 && !hasStartedNewTask) { 1512 // We are about to launch the current running task, so use LAST_TASK 1513 // state instead of NEW_TASK. This could happen, for example, if our 1514 // scroll is aborted after we determined the target to be NEW_TASK. 1515 mGestureState.setEndTarget(LAST_TASK); 1516 } else if (target == LAST_TASK && hasStartedNewTask) { 1517 // We are about to re-launch the previously running task, but we can't 1518 // just finish the controller like we normally would because that would 1519 // instead resume the last task that appeared, and not ensure that this 1520 // task is restored to the top. To address this, re-launch the task as 1521 // if it were a new task. 1522 mGestureState.setEndTarget(NEW_TASK); 1523 } 1524 } 1525 mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); 1526 } 1527 }); 1528 animatorSet.play(windowAnim); 1529 if (mRecentsView != null) { 1530 mRecentsView.onPrepareGestureEndAnimation( 1531 animatorSet, mGestureState.getEndTarget(), 1532 getRemoteTaskViewSimulators()); 1533 } 1534 animatorSet.setDuration(duration).setInterpolator(interpolator); 1535 animatorSet.start(); 1536 mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)}; 1537 } 1538 } 1539 1540 private int calculateWindowRotation(RemoteAnimationTarget runningTaskTarget, 1541 RecentsOrientedState orientationState) { 1542 if (runningTaskTarget.rotationChange != 0 1543 && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 1544 return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90 1545 ? ROTATION_270 : ROTATION_90; 1546 } else { 1547 return orientationState.getDisplayRotation(); 1548 } 1549 } 1550 1551 @Nullable 1552 private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory, 1553 RemoteAnimationTarget runningTaskTarget, float startProgress) { 1554 // Directly animate the app to PiP (picture-in-picture) mode 1555 final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo; 1556 final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator() 1557 .getOrientationState(); 1558 final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState); 1559 final int homeRotation = orientationState.getRecentsActivityRotation(); 1560 1561 final Matrix[] homeToWindowPositionMaps = new Matrix[mRemoteTargetHandles.length]; 1562 final RectF startRect = updateProgressForStartRect(homeToWindowPositionMaps, 1563 startProgress)[0]; 1564 final Matrix homeToWindowPositionMap = homeToWindowPositionMaps[0]; 1565 // Move the startRect to Launcher space as floatingIconView runs in Launcher 1566 final Matrix windowToHomePositionMap = new Matrix(); 1567 homeToWindowPositionMap.invert(windowToHomePositionMap); 1568 windowToHomePositionMap.mapRect(startRect); 1569 1570 final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat(); 1571 final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext) 1572 .startSwipePipToHome(taskInfo.topActivity, 1573 taskInfo.topActivityInfo, 1574 runningTaskTarget.taskInfo.pictureInPictureParams, 1575 homeRotation, 1576 hotseatKeepClearArea); 1577 if (destinationBounds == null) { 1578 // No destination bounds returned from SystemUI, bail early. 1579 return null; 1580 } 1581 final Rect appBounds = new Rect(); 1582 final WindowConfiguration winConfig = taskInfo.configuration.windowConfiguration; 1583 // Adjust the appBounds for TaskBar by using the calculated window crop Rect 1584 // from TaskViewSimulator and fallback to the bounds in TaskInfo when it's originated 1585 // from windowing modes other than full-screen. 1586 if (winConfig.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) { 1587 mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().round(appBounds); 1588 } else { 1589 appBounds.set(winConfig.getBounds()); 1590 } 1591 final SwipePipToHomeAnimator.Builder builder = new SwipePipToHomeAnimator.Builder() 1592 .setContext(mContext) 1593 .setTaskId(runningTaskTarget.taskId) 1594 .setActivityInfo(taskInfo.topActivityInfo) 1595 .setAppIconSizePx(mDp.iconSizePx) 1596 .setLeash(runningTaskTarget.leash) 1597 .setSourceRectHint( 1598 runningTaskTarget.taskInfo.pictureInPictureParams.getSourceRectHint()) 1599 .setAppBounds(appBounds) 1600 .setHomeToWindowPositionMap(homeToWindowPositionMap) 1601 .setStartBounds(startRect) 1602 .setDestinationBounds(destinationBounds) 1603 .setCornerRadius(mRecentsView.getPipCornerRadius()) 1604 .setShadowRadius(mRecentsView.getPipShadowRadius()) 1605 .setAttachedView(mRecentsView); 1606 // We would assume home and app window always in the same rotation While homeRotation 1607 // is not ROTATION_0 (which implies the rotation is turned on in launcher settings). 1608 if (homeRotation == ROTATION_0 1609 && (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) { 1610 builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation, 1611 taskInfo.displayCutoutInsets); 1612 } 1613 final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build(); 1614 AnimatorPlaybackController activityAnimationToHome = 1615 homeAnimFactory.createActivityAnimationToHome(); 1616 swipePipToHomeAnimator.addAnimatorListener(new AnimatorListenerAdapter() { 1617 private boolean mHasAnimationEnded; 1618 @Override 1619 public void onAnimationStart(Animator animation) { 1620 if (mHasAnimationEnded) return; 1621 // Ensure Launcher ends in NORMAL state 1622 activityAnimationToHome.dispatchOnStart(); 1623 } 1624 1625 @Override 1626 public void onAnimationEnd(Animator animation) { 1627 if (mHasAnimationEnded) return; 1628 mHasAnimationEnded = true; 1629 activityAnimationToHome.getAnimationPlayer().end(); 1630 if (mRecentsAnimationController == null) { 1631 // If the recents animation is interrupted, we still end the running 1632 // animation (not canceled) so this is still called. In that case, we can 1633 // skip doing any future work here for the current gesture. 1634 return; 1635 } 1636 // Finalize the state and notify of the change 1637 mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); 1638 } 1639 }); 1640 setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator}); 1641 return swipePipToHomeAnimator; 1642 } 1643 1644 private Rect getKeepClearAreaForHotseat() { 1645 Rect keepClearArea; 1646 if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { 1647 // make the height equal to hotseatBarSizePx only 1648 keepClearArea = new Rect(0, 0, 0, mDp.hotseatBarSizePx); 1649 return keepClearArea; 1650 } 1651 // the keep clear area in global screen coordinates, in pixels 1652 if (mDp.isPhone) { 1653 if (mDp.isSeascape()) { 1654 // in seascape the Hotseat is on the left edge of the screen 1655 keepClearArea = new Rect(0, 0, mDp.hotseatBarSizePx, mDp.heightPx); 1656 } else if (mDp.isLandscape) { 1657 // in landscape the Hotseat is on the right edge of the screen 1658 keepClearArea = new Rect(mDp.widthPx - mDp.hotseatBarSizePx, 0, 1659 mDp.widthPx, mDp.heightPx); 1660 } else { 1661 // in portrait mode the Hotseat is at the bottom of the screen 1662 keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx, 1663 mDp.widthPx, mDp.heightPx); 1664 } 1665 } else { 1666 // large screens have Hotseat always at the bottom of the screen 1667 keepClearArea = new Rect(0, mDp.heightPx - mDp.hotseatBarSizePx, 1668 mDp.widthPx, mDp.heightPx); 1669 } 1670 return keepClearArea; 1671 } 1672 1673 private void startInterceptingTouchesForGesture() { 1674 if (mRecentsAnimationController == null) { 1675 return; 1676 } 1677 1678 mRecentsAnimationController.enableInputConsumer(); 1679 } 1680 1681 private void computeRecentsScrollIfInvisible() { 1682 if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) { 1683 // Views typically don't compute scroll when invisible as an optimization, 1684 // but in our case we need to since the window offset depends on the scroll. 1685 mRecentsView.computeScroll(); 1686 } 1687 } 1688 1689 private void continueComputingRecentsScrollIfNecessary() { 1690 if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED) 1691 && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED) 1692 && !mCanceled) { 1693 computeRecentsScrollIfInvisible(); 1694 mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary); 1695 } 1696 } 1697 1698 /** 1699 * Creates an animation that transforms the current app window into the home app. 1700 * @param startProgress The progress of {@link #mCurrentShift} to start the window from. 1701 * @param homeAnimationFactory The home animation factory. 1702 */ 1703 @Override 1704 protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress, 1705 HomeAnimationFactory homeAnimationFactory) { 1706 RectFSpringAnim[] anim = 1707 super.createWindowAnimationToHome(startProgress, homeAnimationFactory); 1708 setupWindowAnimation(anim); 1709 return anim; 1710 } 1711 1712 private void setupWindowAnimation(RectFSpringAnim[] anims) { 1713 anims[0].addOnUpdateListener((r, p) -> { 1714 updateSysUiFlags(Math.max(p, mCurrentShift.value)); 1715 }); 1716 anims[0].addAnimatorListener(new AnimationSuccessListener() { 1717 @Override 1718 public void onAnimationSuccess(Animator animator) { 1719 if (mRecentsView != null) { 1720 mRecentsView.post(mRecentsView::resetTaskVisuals); 1721 } 1722 // Make sure recents is in its final state 1723 maybeUpdateRecentsAttachedState(false); 1724 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState); 1725 } 1726 }); 1727 if (mRecentsAnimationTargets != null) { 1728 mRecentsAnimationTargets.addReleaseCheck(anims[0]); 1729 } 1730 } 1731 1732 public void onConsumerAboutToBeSwitched() { 1733 if (mActivity != null) { 1734 // In the off chance that the gesture ends before Launcher is started, we should clear 1735 // the callback here so that it doesn't update with the wrong state 1736 mActivity.clearRunOnceOnStartCallback(); 1737 resetLauncherListeners(); 1738 } 1739 if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null 1740 && !mGestureState.getEndTarget().isLauncher) { 1741 // Continued quick switch. 1742 cancelCurrentAnimation(); 1743 } else { 1744 mStateCallback.setStateOnUiThread(STATE_FINISH_WITH_NO_END); 1745 reset(); 1746 } 1747 } 1748 1749 public boolean isCanceled() { 1750 return mCanceled; 1751 } 1752 1753 @UiThread 1754 private void resumeLastTask() { 1755 if (mRecentsAnimationController != null) { 1756 mRecentsAnimationController.finish(false /* toRecents */, null); 1757 } 1758 doLogGesture(LAST_TASK, null); 1759 reset(); 1760 } 1761 1762 @UiThread 1763 private void startNewTask() { 1764 TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView(); 1765 startNewTask(success -> { 1766 if (!success) { 1767 reset(); 1768 // We couldn't launch the task, so take user to overview so they can 1769 // decide what to do instead of staying in this broken state. 1770 endLauncherTransitionController(); 1771 updateSysUiFlags(1 /* windowProgress == overview */); 1772 } 1773 doLogGesture(NEW_TASK, taskToLaunch); 1774 }); 1775 } 1776 1777 /** 1778 * Called when we successfully startNewTask() on the task that was previously running. Normally 1779 * we call resumeLastTask() when returning to the previously running task, but this handles a 1780 * specific edge case: if we switch from A to B, and back to A before B appears, we need to 1781 * start A again to ensure it stays on top. 1782 */ 1783 @androidx.annotation.CallSuper 1784 protected void onRestartPreviouslyAppearedTask() { 1785 // Finish the controller here, since we won't get onTaskAppeared() for a task that already 1786 // appeared. 1787 if (mRecentsAnimationController != null) { 1788 mRecentsAnimationController.finish(false, null); 1789 } 1790 reset(); 1791 } 1792 1793 private void reset() { 1794 mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); 1795 if (mActivity != null) { 1796 mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks); 1797 } 1798 } 1799 1800 /** 1801 * Cancels any running animation so that the active target can be overriden by a new swipe 1802 * handler (in case of quick switch). 1803 */ 1804 private void cancelCurrentAnimation() { 1805 ActiveGestureLog.INSTANCE.addLog( 1806 "AbsSwipeUpHandler.cancelCurrentAnimation", 1807 ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION); 1808 mCanceled = true; 1809 mCurrentShift.cancelAnimation(); 1810 1811 // Cleanup when switching handlers 1812 mInputConsumerProxy.unregisterCallback(); 1813 mActivityInitListener.unregister(); 1814 TaskStackChangeListeners.getInstance().unregisterTaskStackListener( 1815 mActivityRestartListener); 1816 mTaskSnapshot = null; 1817 } 1818 1819 private void invalidateHandler() { 1820 if (!mActivityInterface.isInLiveTileMode() || mGestureState.getEndTarget() != RECENTS) { 1821 mInputConsumerProxy.destroy(); 1822 mTaskAnimationManager.setLiveTileCleanUpHandler(null); 1823 } 1824 mInputConsumerProxy.unregisterCallback(); 1825 endRunningWindowAnim(false /* cancel */); 1826 1827 if (mGestureEndCallback != null) { 1828 mGestureEndCallback.run(); 1829 } 1830 1831 mActivityInitListener.unregister(); 1832 TaskStackChangeListeners.getInstance().unregisterTaskStackListener( 1833 mActivityRestartListener); 1834 mTaskSnapshot = null; 1835 } 1836 1837 private void invalidateHandlerWithLauncher() { 1838 endLauncherTransitionController(); 1839 1840 mRecentsView.onGestureAnimationEnd(); 1841 resetLauncherListeners(); 1842 } 1843 1844 private void endLauncherTransitionController() { 1845 mHasEndedLauncherTransition = true; 1846 1847 if (mLauncherTransitionController != null) { 1848 // End the animation, but stay at the same visual progress. 1849 mLauncherTransitionController.getNormalController().dispatchSetInterpolator( 1850 t -> Utilities.boundToRange(mCurrentShift.value, 0, 1)); 1851 mLauncherTransitionController.getNormalController().getAnimationPlayer().end(); 1852 mLauncherTransitionController = null; 1853 } 1854 1855 if (mRecentsView != null) { 1856 mRecentsView.abortScrollerAnimation(); 1857 } 1858 } 1859 1860 /** 1861 * Unlike invalidateHandlerWithLauncher, this is called even when switching consumers, e.g. on 1862 * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it. 1863 */ 1864 private void resetLauncherListeners() { 1865 mActivity.getRootView().setOnApplyWindowInsetsListener(null); 1866 1867 mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener); 1868 } 1869 1870 private void resetStateForAnimationCancel() { 1871 boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted; 1872 mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget()); 1873 1874 // Leave the pending invisible flag, as it may be used by wallpaper open animation. 1875 if (mActivity != null) { 1876 mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER); 1877 } 1878 } 1879 1880 protected void switchToScreenshot() { 1881 if (!hasTargets()) { 1882 // If there are no targets, then we don't need to capture anything 1883 mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); 1884 } else { 1885 final int runningTaskId = mGestureState.getRunningTaskId(); 1886 boolean finishTransitionPosted = false; 1887 if (mRecentsAnimationController != null) { 1888 // Update the screenshot of the task 1889 if (mTaskSnapshot == null) { 1890 UI_HELPER_EXECUTOR.execute(() -> { 1891 if (mRecentsAnimationController == null) return; 1892 final ThumbnailData taskSnapshot = 1893 mRecentsAnimationController.screenshotTask(runningTaskId); 1894 // If split case, we should update all split tasks snapshot 1895 if (mIsSwipeForSplit) { 1896 int[] splitTaskIds = TopTaskTracker.INSTANCE.get( 1897 mContext).getRunningSplitTaskIds(); 1898 for (int i = 0; i < splitTaskIds.length; i++) { 1899 // Skip running one because done above. 1900 if (splitTaskIds[i] == runningTaskId) continue; 1901 1902 mRecentsAnimationController.screenshotTask(splitTaskIds[i]); 1903 } 1904 } 1905 MAIN_EXECUTOR.execute(() -> { 1906 mTaskSnapshot = taskSnapshot; 1907 if (!updateThumbnail(runningTaskId, false /* refreshView */)) { 1908 setScreenshotCapturedState(); 1909 } 1910 }); 1911 }); 1912 return; 1913 } 1914 finishTransitionPosted = updateThumbnail(runningTaskId, false /* refreshView */); 1915 } 1916 if (!finishTransitionPosted) { 1917 setScreenshotCapturedState(); 1918 } 1919 } 1920 } 1921 1922 // Returns whether finish transition was posted. 1923 private boolean updateThumbnail(int runningTaskId, boolean refreshView) { 1924 boolean finishTransitionPosted = false; 1925 final TaskView taskView; 1926 if (mGestureState.getEndTarget() == HOME || mGestureState.getEndTarget() == NEW_TASK) { 1927 // Capture the screenshot before finishing the transition to home or quickswitching to 1928 // ensure it's taken in the correct orientation, but no need to update the thumbnail. 1929 taskView = null; 1930 } else { 1931 taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, refreshView); 1932 } 1933 if (taskView != null && refreshView && !mCanceled) { 1934 // Defer finishing the animation until the next launcher frame with the 1935 // new thumbnail 1936 finishTransitionPosted = ViewUtils.postFrameDrawn(taskView, 1937 () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), 1938 this::isCanceled); 1939 } 1940 return finishTransitionPosted; 1941 } 1942 1943 private void setScreenshotCapturedState() { 1944 // If we haven't posted a draw callback, set the state immediately. 1945 Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT, 1946 TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS); 1947 mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); 1948 TraceHelper.INSTANCE.endSection(traceToken); 1949 } 1950 1951 private void finishCurrentTransitionToRecents() { 1952 if (mRecentsView != null 1953 && mActivityInterface.getDesktopVisibilityController() != null 1954 && mActivityInterface.getDesktopVisibilityController().areFreeformTasksVisible()) { 1955 mRecentsView.switchToScreenshot(() -> { 1956 mRecentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 1957 () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); 1958 }); 1959 } else { 1960 mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); 1961 if (mRecentsAnimationController != null) { 1962 mRecentsAnimationController.detachNavigationBarFromApp(true); 1963 } 1964 } 1965 } 1966 1967 private void finishCurrentTransitionToHome() { 1968 if (!hasTargets() || mRecentsAnimationController == null) { 1969 // If there are no targets or the animation not started, then there is nothing to finish 1970 mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); 1971 } else { 1972 maybeFinishSwipeToHome(); 1973 finishRecentsControllerToHome( 1974 () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); 1975 } 1976 if (mSwipePipToHomeReleaseCheck != null) { 1977 mSwipePipToHomeReleaseCheck.setCanRelease(true); 1978 mSwipePipToHomeReleaseCheck = null; 1979 } 1980 doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView()); 1981 } 1982 1983 /** 1984 * Notifies SysUI that transition is finished if applicable and also pass leash transactions 1985 * from Launcher to WM. 1986 * This should happen before {@link #finishRecentsControllerToHome(Runnable)}. 1987 */ 1988 private void maybeFinishSwipeToHome() { 1989 if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) { 1990 SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome( 1991 mSwipePipToHomeAnimator.getTaskId(), 1992 mSwipePipToHomeAnimator.getComponentName(), 1993 mSwipePipToHomeAnimator.getDestinationBounds(), 1994 mSwipePipToHomeAnimator.getContentOverlay()); 1995 mRecentsAnimationController.setFinishTaskTransaction( 1996 mSwipePipToHomeAnimator.getTaskId(), 1997 mSwipePipToHomeAnimator.getFinishTransaction(), 1998 mSwipePipToHomeAnimator.getContentOverlay()); 1999 mIsSwipingPipToHome = false; 2000 } else if (mIsSwipeForSplit) { 2001 // Transaction to hide the task to avoid flicker for entering PiP from split-screen. 2002 PictureInPictureSurfaceTransaction tx = 2003 new PictureInPictureSurfaceTransaction.Builder() 2004 .setAlpha(0f) 2005 .build(); 2006 tx.setShouldDisableCanAffectSystemUiFlags(false); 2007 int[] taskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds(); 2008 for (int taskId : taskIds) { 2009 mRecentsAnimationController.setFinishTaskTransaction(taskId, 2010 tx, null /* overlay */); 2011 } 2012 } 2013 } 2014 2015 protected abstract void finishRecentsControllerToHome(Runnable callback); 2016 2017 private void setupLauncherUiAfterSwipeUpToRecentsAnimation() { 2018 if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { 2019 return; 2020 } 2021 endLauncherTransitionController(); 2022 mRecentsView.onSwipeUpAnimationSuccess(); 2023 mTaskAnimationManager.setLiveTileCleanUpHandler(() -> { 2024 mRecentsView.cleanupRemoteTargets(); 2025 mInputConsumerProxy.destroy(); 2026 }); 2027 mTaskAnimationManager.enableLiveTileRestartListener(); 2028 2029 SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG); 2030 doLogGesture(RECENTS, mRecentsView.getCurrentPageTaskView()); 2031 reset(); 2032 } 2033 2034 private static boolean isNotInRecents(RemoteAnimationTarget app) { 2035 return app.isNotInRecents 2036 || app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME; 2037 } 2038 2039 protected void performHapticFeedback() { 2040 VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC); 2041 } 2042 2043 public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) { 2044 return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null; 2045 } 2046 2047 public void setGestureEndCallback(Runnable gestureEndCallback) { 2048 mGestureEndCallback = gestureEndCallback; 2049 } 2050 2051 protected void linkRecentsViewScroll() { 2052 SurfaceTransactionApplier.create(mRecentsView, applier -> { 2053 runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams() 2054 .setSyncTransactionApplier(applier)); 2055 runOnRecentsAnimationAndLauncherBound(() -> 2056 mRecentsAnimationTargets.addReleaseCheck(applier)); 2057 }); 2058 2059 mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener); 2060 runOnRecentsAnimationAndLauncherBound(() -> 2061 mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController, 2062 mRecentsAnimationTargets)); 2063 mRecentsViewScrollLinked = true; 2064 } 2065 2066 private void onRecentsViewScroll() { 2067 if (moveWindowWithRecentsScroll()) { 2068 updateFinalShift(); 2069 } 2070 } 2071 2072 protected void startNewTask(Consumer<Boolean> resultCallback) { 2073 // Launch the task user scrolled to (mRecentsView.getNextPage()). 2074 if (!mCanceled) { 2075 TaskView nextTask = mRecentsView.getNextPageTaskView(); 2076 if (nextTask != null) { 2077 int taskId = nextTask.getTask().key.id; 2078 mGestureState.updateLastStartedTaskId(taskId); 2079 boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds() 2080 .contains(taskId); 2081 if (!hasTaskPreviouslyAppeared) { 2082 ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED); 2083 } 2084 nextTask.launchTask(success -> { 2085 resultCallback.accept(success); 2086 if (success) { 2087 if (hasTaskPreviouslyAppeared) { 2088 onRestartPreviouslyAppearedTask(); 2089 } 2090 } else { 2091 mActivityInterface.onLaunchTaskFailed(); 2092 if (mRecentsAnimationController != null) { 2093 mRecentsAnimationController.finish(true /* toRecents */, null); 2094 } 2095 } 2096 }, true /* freezeTaskList */); 2097 } else { 2098 mActivityInterface.onLaunchTaskFailed(); 2099 Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show(); 2100 if (mRecentsAnimationController != null) { 2101 mRecentsAnimationController.finish(true /* toRecents */, null); 2102 } 2103 } 2104 } 2105 mCanceled = false; 2106 } 2107 2108 /** 2109 * Runs the given {@param action} if the recents animation has already started and Launcher has 2110 * been created and bound to the TouchInteractionService, or queues it to be run when it this 2111 * next happens. 2112 */ 2113 private void runOnRecentsAnimationAndLauncherBound(Runnable action) { 2114 mRecentsAnimationStartCallbacks.add(action); 2115 flushOnRecentsAnimationAndLauncherBound(); 2116 } 2117 2118 private void flushOnRecentsAnimationAndLauncherBound() { 2119 if (mRecentsAnimationTargets == null || 2120 !mStateCallback.hasStates(STATE_LAUNCHER_BIND_TO_SERVICE)) { 2121 return; 2122 } 2123 2124 if (!mRecentsAnimationStartCallbacks.isEmpty()) { 2125 for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) { 2126 action.run(); 2127 } 2128 mRecentsAnimationStartCallbacks.clear(); 2129 } 2130 } 2131 2132 /** 2133 * TODO can we remove this now that we don't finish the controller until onTaskAppeared()? 2134 * @return whether the recents animation has started and there are valid app targets. 2135 */ 2136 protected boolean hasTargets() { 2137 return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets(); 2138 } 2139 2140 @Override 2141 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 2142 mRecentsAnimationController = null; 2143 mRecentsAnimationTargets = null; 2144 if (mRecentsView != null) { 2145 mRecentsView.setRecentsAnimationTargets(null, null); 2146 } 2147 } 2148 2149 @Override 2150 public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) { 2151 if (mRecentsAnimationController != null) { 2152 if (handleTaskAppeared(appearedTaskTargets)) { 2153 Optional<RemoteAnimationTarget> taskTargetOptional = 2154 Arrays.stream(appearedTaskTargets) 2155 .filter(targetCompat -> 2156 targetCompat.taskId == mGestureState.getLastStartedTaskId()) 2157 .findFirst(); 2158 if (!taskTargetOptional.isPresent()) { 2159 finishRecentsAnimationOnTasksAppeared(); 2160 return; 2161 } 2162 RemoteAnimationTarget taskTarget = taskTargetOptional.get(); 2163 TaskView taskView = mRecentsView.getTaskViewByTaskId(taskTarget.taskId); 2164 if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) { 2165 finishRecentsAnimationOnTasksAppeared(); 2166 return; 2167 } 2168 2169 ViewGroup splashView = mActivity.getDragLayer(); 2170 2171 // When revealing the app with launcher splash screen, make the app visible 2172 // and behind the splash view before the splash is animated away. 2173 SurfaceTransactionApplier surfaceApplier = 2174 new SurfaceTransactionApplier(splashView); 2175 SurfaceTransaction transaction = new SurfaceTransaction(); 2176 for (RemoteAnimationTarget target : appearedTaskTargets) { 2177 transaction.forSurface(target.leash).setAlpha(1).setLayer(-1); 2178 } 2179 surfaceApplier.scheduleApply(transaction); 2180 2181 SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash, 2182 mSplashMainWindowShiftLength, new TransactionPool(), new Rect(), 2183 SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION, 2184 /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0, 2185 SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION, 2186 new AnimatorListenerAdapter() { 2187 @Override 2188 public void onAnimationEnd(Animator animation) { 2189 finishRecentsAnimationOnTasksAppeared(); 2190 } 2191 }); 2192 } 2193 } 2194 } 2195 2196 private void finishRecentsAnimationOnTasksAppeared() { 2197 if (mRecentsAnimationController != null) { 2198 mRecentsAnimationController.finish(false /* toRecents */, null /* onFinishComplete */); 2199 } 2200 ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false); 2201 } 2202 2203 /** 2204 * @return The index of the TaskView in RecentsView whose taskId matches the task that will 2205 * resume if we finish the controller. 2206 */ 2207 protected int getLastAppearedTaskIndex() { 2208 return mGestureState.getLastAppearedTaskId() != -1 2209 ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId()) 2210 : mRecentsView.getRunningTaskIndex(); 2211 } 2212 2213 /** 2214 * @return Whether we are continuing a gesture that already landed on a new task, 2215 * but before that task appeared. 2216 */ 2217 protected boolean hasStartedNewTask() { 2218 return mGestureState.getLastStartedTaskId() != -1; 2219 } 2220 2221 /** 2222 * Registers a callback to run when the activity is ready. 2223 */ 2224 public void initWhenReady() { 2225 // Preload the plan 2226 RecentsModel.INSTANCE.get(mContext).getTasks(null); 2227 2228 mActivityInitListener.register(); 2229 } 2230 2231 /** 2232 * Applies the transform on the recents animation 2233 */ 2234 protected void applyScrollAndTransform() { 2235 // No need to apply any transform if there is ongoing swipe-to-home animator 2236 // swipe-to-pip handles the leash solely 2237 // swipe-to-icon animation is handled by RectFSpringAnim anim 2238 boolean notSwipingToHome = mRecentsAnimationTargets != null 2239 && mGestureState.getEndTarget() != HOME; 2240 boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null; 2241 float progress = Math.max(mCurrentShift.value, getScaleProgressDueToScroll()); 2242 int scrollOffset = setRecentsScroll ? mRecentsView.getScrollOffset() : 0; 2243 if (progress > 0 || scrollOffset != 0) { 2244 // Hide the divider as the tasks start moving. 2245 setDividerShown(false); 2246 } 2247 for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) { 2248 AnimatorControllerWithResistance playbackController = 2249 remoteHandle.getPlaybackController(); 2250 if (playbackController != null) { 2251 playbackController.setProgress(progress, mDragLengthFactor); 2252 } 2253 2254 if (notSwipingToHome) { 2255 TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator(); 2256 if (setRecentsScroll) { 2257 taskViewSimulator.setScroll(scrollOffset); 2258 } 2259 taskViewSimulator.apply(remoteHandle.getTransformParams()); 2260 } 2261 } 2262 ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate(); 2263 } 2264 2265 // Scaling of RecentsView during quick switch based on amount of recents scroll 2266 private float getScaleProgressDueToScroll() { 2267 if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null 2268 || !mRecentsViewScrollLinked) { 2269 return 0; 2270 } 2271 2272 float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage())); 2273 int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue( 2274 mRecentsView.getLastComputedTaskSize().width(), 2275 mRecentsView.getLastComputedTaskSize().height()); 2276 maxScrollOffset += mRecentsView.getPageSpacing(); 2277 2278 float maxScaleProgress = 2279 MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS * mRecentsView.getMaxScaleForFullScreen(); 2280 float scaleProgress = maxScaleProgress; 2281 2282 if (scrollOffset < mQuickSwitchScaleScrollThreshold) { 2283 scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold, 2284 0, maxScaleProgress, ACCEL_DEACCEL); 2285 } else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) { 2286 scaleProgress = Utilities.mapToRange(scrollOffset, 2287 (maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset, 2288 maxScaleProgress, 0, ACCEL_DEACCEL); 2289 } 2290 2291 return scaleProgress; 2292 } 2293 2294 /** 2295 * Overrides the gesture displacement to keep the app window at the bottom of the screen while 2296 * the transient taskbar is being swiped in. 2297 * 2298 * There is also a catch up period so that the window can start moving 1:1 with the swipe. 2299 */ 2300 @Override 2301 protected float overrideDisplacementForTransientTaskbar(float displacement) { 2302 if (!mIsTransientTaskbar) { 2303 return displacement; 2304 } 2305 2306 if (mTaskbarAlreadyOpen || mIsTaskbarAllAppsOpen) { 2307 return displacement; 2308 } 2309 2310 if (displacement < mTaskbarAppWindowThreshold) { 2311 return 0; 2312 } 2313 2314 // "Catch up" with the displacement at mTaskbarCatchUpThreshold. 2315 if (displacement < mTaskbarCatchUpThreshold) { 2316 return Utilities.mapToRange(displacement, mTaskbarAppWindowThreshold, 2317 mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCEL_DEACCEL); 2318 } 2319 2320 return displacement; 2321 } 2322 2323 private void setDividerShown(boolean shown) { 2324 if (mRecentsAnimationTargets == null || mIsDividerShown == shown) { 2325 return; 2326 } 2327 mIsDividerShown = shown; 2328 TaskViewUtils.createSplitAuxiliarySurfacesAnimator( 2329 mRecentsAnimationTargets.nonApps, shown, null /* animatorHandler */); 2330 } 2331 2332 /** 2333 * Used for winscope tracing, see launcher_trace.proto 2334 * @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto 2335 * @param inputConsumerProto The parent of this proto message. 2336 */ 2337 public void writeToProto(InputConsumerProto.Builder inputConsumerProto) { 2338 SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder(); 2339 2340 mGestureState.writeToProto(swipeHandlerProto); 2341 2342 swipeHandlerProto.setIsRecentsAttachedToAppWindow( 2343 mAnimationFactory.isRecentsAttachedToAppWindow()); 2344 swipeHandlerProto.setScrollOffset(mRecentsView == null 2345 ? 0 2346 : mRecentsView.getScrollOffset()); 2347 swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value); 2348 2349 inputConsumerProto.setSwipeHandler(swipeHandlerProto); 2350 } 2351 2352 public interface Factory { 2353 AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs); 2354 } 2355 } 2356