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