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