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