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