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.launcher3.touch; 17 18 import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS; 19 import static com.android.launcher3.LauncherState.ALL_APPS; 20 import static com.android.launcher3.LauncherState.NORMAL; 21 import static com.android.launcher3.LauncherState.OVERVIEW; 22 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; 23 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS; 24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN; 25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP; 26 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS; 27 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE; 28 import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC; 29 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; 30 31 import android.animation.Animator; 32 import android.animation.AnimatorListenerAdapter; 33 import android.animation.AnimatorSet; 34 import android.animation.ValueAnimator; 35 import android.os.SystemClock; 36 import android.util.Log; 37 import android.view.HapticFeedbackConstants; 38 import android.view.MotionEvent; 39 40 import com.android.launcher3.Launcher; 41 import com.android.launcher3.LauncherAnimUtils; 42 import com.android.launcher3.LauncherState; 43 import com.android.launcher3.Utilities; 44 import com.android.launcher3.anim.AnimationSuccessListener; 45 import com.android.launcher3.anim.AnimatorPlaybackController; 46 import com.android.launcher3.anim.PendingAnimation; 47 import com.android.launcher3.logger.LauncherAtom; 48 import com.android.launcher3.logging.StatsLogManager; 49 import com.android.launcher3.states.StateAnimationConfig; 50 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags; 51 import com.android.launcher3.testing.TestProtocol; 52 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; 53 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; 54 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 55 import com.android.launcher3.util.FlingBlockCheck; 56 import com.android.launcher3.util.TouchController; 57 58 /** 59 * TouchController for handling state changes 60 */ 61 public abstract class AbstractStateChangeTouchController 62 implements TouchController, SingleAxisSwipeDetector.Listener { 63 64 // Progress after which the transition is assumed to be a success in case user does not fling 65 public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; 66 67 /** 68 * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this. 69 */ 70 public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 0.5f; 71 protected final long ATOMIC_DURATION = getAtomicDuration(); 72 73 protected final Launcher mLauncher; 74 protected final SingleAxisSwipeDetector mDetector; 75 protected final SingleAxisSwipeDetector.Direction mSwipeDirection; 76 77 private boolean mNoIntercept; 78 private boolean mIsLogContainerSet; 79 protected int mStartContainerType; 80 81 protected LauncherState mStartState; 82 protected LauncherState mFromState; 83 protected LauncherState mToState; 84 protected AnimatorPlaybackController mCurrentAnimation; 85 protected PendingAnimation mPendingAnimation; 86 87 private float mStartProgress; 88 // Ratio of transition process [0, 1] to drag displacement (px) 89 private float mProgressMultiplier; 90 private float mDisplacementShift; 91 private boolean mCanBlockFling; 92 private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck(); 93 94 protected AnimatorSet mAtomicAnim; 95 // True if we want to resume playing atomic components when mAtomicAnim completes. 96 private boolean mScheduleResumeAtomicComponent; 97 private AutoPlayAtomicAnimationInfo mAtomicAnimAutoPlayInfo; 98 99 private boolean mPassedOverviewAtomicThreshold; 100 // mAtomicAnim plays the atomic components of the state animations when we pass the threshold. 101 // However, if we reinit to transition to a new state (e.g. OVERVIEW -> ALL_APPS) before the 102 // atomic animation finishes, we only control the non-atomic components so that we don't 103 // interfere with the atomic animation. When the atomic animation ends, we start controlling 104 // the atomic components as well, using this controller. 105 private AnimatorPlaybackController mAtomicComponentsController; 106 private LauncherState mAtomicComponentsTargetState = NORMAL; 107 108 private float mAtomicComponentsStartProgress; 109 AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir)110 public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) { 111 mLauncher = l; 112 mDetector = new SingleAxisSwipeDetector(l, this, dir); 113 mSwipeDirection = dir; 114 } 115 getAtomicDuration()116 protected long getAtomicDuration() { 117 return 200; 118 } 119 canInterceptTouch(MotionEvent ev)120 protected abstract boolean canInterceptTouch(MotionEvent ev); 121 122 @Override onControllerInterceptTouchEvent(MotionEvent ev)123 public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { 124 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 125 mNoIntercept = !canInterceptTouch(ev); 126 if (mNoIntercept) { 127 return false; 128 } 129 130 // Now figure out which direction scroll events the controller will start 131 // calling the callbacks. 132 final int directionsToDetectScroll; 133 boolean ignoreSlopWhenSettling = false; 134 135 if (mCurrentAnimation != null) { 136 directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH; 137 ignoreSlopWhenSettling = true; 138 } else { 139 directionsToDetectScroll = getSwipeDirection(); 140 if (directionsToDetectScroll == 0) { 141 mNoIntercept = true; 142 return false; 143 } 144 } 145 mDetector.setDetectableScrollConditions( 146 directionsToDetectScroll, ignoreSlopWhenSettling); 147 } 148 149 if (mNoIntercept) { 150 return false; 151 } 152 153 onControllerTouchEvent(ev); 154 return mDetector.isDraggingOrSettling(); 155 } 156 getSwipeDirection()157 private int getSwipeDirection() { 158 LauncherState fromState = mLauncher.getStateManager().getState(); 159 int swipeDirection = 0; 160 if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) { 161 swipeDirection |= SingleAxisSwipeDetector.DIRECTION_POSITIVE; 162 } 163 if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) { 164 swipeDirection |= SingleAxisSwipeDetector.DIRECTION_NEGATIVE; 165 } 166 return swipeDirection; 167 } 168 169 @Override onControllerTouchEvent(MotionEvent ev)170 public final boolean onControllerTouchEvent(MotionEvent ev) { 171 if (TestProtocol.sDebugTracing) { 172 Log.d(TestProtocol.PAUSE_NOT_DETECTED, "onControllerTouchEvent"); 173 } 174 return mDetector.onTouchEvent(ev); 175 } 176 getShiftRange()177 protected float getShiftRange() { 178 return mLauncher.getAllAppsController().getShiftRange(); 179 } 180 181 /** 182 * Returns the state to go to from fromState given the drag direction. If there is no state in 183 * that direction, returns fromState. 184 */ getTargetState(LauncherState fromState, boolean isDragTowardPositive)185 protected abstract LauncherState getTargetState(LauncherState fromState, 186 boolean isDragTowardPositive); 187 initCurrentAnimation(@nimationFlags int animComponents)188 protected abstract float initCurrentAnimation(@AnimationFlags int animComponents); 189 190 /** 191 * Returns the container that the touch started from when leaving NORMAL state. 192 */ getLogContainerTypeForNormalState(MotionEvent ev)193 protected abstract int getLogContainerTypeForNormalState(MotionEvent ev); 194 reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive)195 private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) { 196 LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState() 197 : reachedToState ? mToState : mFromState; 198 LauncherState newToState = getTargetState(newFromState, isDragTowardPositive); 199 200 if (newFromState == mFromState && newToState == mToState || (newFromState == newToState)) { 201 return false; 202 } 203 204 mFromState = newFromState; 205 mToState = newToState; 206 if (TestProtocol.sDebugTracing) { 207 Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "reinitCurrentAnimation: " 208 + newToState.ordinal + " " + getClass().getSimpleName()); 209 } 210 211 mStartProgress = 0; 212 mPassedOverviewAtomicThreshold = false; 213 if (mCurrentAnimation != null) { 214 mCurrentAnimation.setOnCancelRunnable(null); 215 } 216 int animComponents = goingBetweenNormalAndOverview(mFromState, mToState) 217 ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS; 218 mScheduleResumeAtomicComponent = false; 219 if (mAtomicAnim != null) { 220 animComponents = PLAY_NON_ATOMIC; 221 // Control the non-atomic components until the atomic animation finishes, then control 222 // the atomic components as well. 223 mScheduleResumeAtomicComponent = true; 224 } 225 if (goingBetweenNormalAndOverview(mFromState, mToState) 226 || mAtomicComponentsTargetState != mToState) { 227 cancelAtomicComponentsController(); 228 } 229 230 if (mAtomicComponentsController != null) { 231 animComponents &= ~PLAY_ATOMIC_OVERVIEW_SCALE; 232 } 233 mProgressMultiplier = initCurrentAnimation(animComponents); 234 mCurrentAnimation.dispatchOnStart(); 235 return true; 236 } 237 goingBetweenNormalAndOverview(LauncherState fromState, LauncherState toState)238 protected boolean goingBetweenNormalAndOverview(LauncherState fromState, 239 LauncherState toState) { 240 return (fromState == NORMAL || fromState == OVERVIEW) 241 && (toState == NORMAL || toState == OVERVIEW) 242 && mPendingAnimation == null; 243 } 244 245 @Override onDragStart(boolean start, float startDisplacement)246 public void onDragStart(boolean start, float startDisplacement) { 247 mStartState = mLauncher.getStateManager().getState(); 248 mIsLogContainerSet = false; 249 250 if (mCurrentAnimation == null) { 251 mFromState = mStartState; 252 mToState = null; 253 cancelAnimationControllers(); 254 reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive()); 255 mDisplacementShift = 0; 256 } else { 257 mCurrentAnimation.pause(); 258 mStartProgress = mCurrentAnimation.getProgressFraction(); 259 260 mAtomicAnimAutoPlayInfo = null; 261 if (mAtomicComponentsController != null) { 262 mAtomicComponentsController.pause(); 263 } 264 } 265 mCanBlockFling = mFromState == NORMAL; 266 mFlingBlockCheck.unblockFling(); 267 // Must be called after all the animation controllers have been paused 268 if (mToState == ALL_APPS || mToState == NORMAL) { 269 mLauncher.getAllAppsController().onDragStart(mToState == ALL_APPS); 270 } 271 } 272 273 @Override onDrag(float displacement)274 public boolean onDrag(float displacement) { 275 float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift); 276 float progress = deltaProgress + mStartProgress; 277 updateProgress(progress); 278 boolean isDragTowardPositive = mSwipeDirection.isPositive( 279 displacement - mDisplacementShift); 280 if (progress <= 0) { 281 if (reinitCurrentAnimation(false, isDragTowardPositive)) { 282 mDisplacementShift = displacement; 283 if (mCanBlockFling) { 284 mFlingBlockCheck.blockFling(); 285 } 286 } 287 } else if (progress >= 1) { 288 if (reinitCurrentAnimation(true, isDragTowardPositive)) { 289 mDisplacementShift = displacement; 290 if (mCanBlockFling) { 291 mFlingBlockCheck.blockFling(); 292 } 293 } 294 } else { 295 mFlingBlockCheck.onEvent(); 296 } 297 298 return true; 299 } 300 301 @Override onDrag(float displacement, MotionEvent ev)302 public boolean onDrag(float displacement, MotionEvent ev) { 303 if (!mIsLogContainerSet) { 304 if (mStartState == ALL_APPS) { 305 mStartContainerType = ContainerType.ALLAPPS; 306 } else if (mStartState == NORMAL) { 307 mStartContainerType = getLogContainerTypeForNormalState(ev); 308 } else if (mStartState == OVERVIEW) { 309 mStartContainerType = ContainerType.TASKSWITCHER; 310 } 311 mIsLogContainerSet = true; 312 } 313 return onDrag(displacement); 314 } 315 updateProgress(float fraction)316 protected void updateProgress(float fraction) { 317 if (mCurrentAnimation == null) { 318 return; 319 } 320 mCurrentAnimation.setPlayFraction(fraction); 321 if (mAtomicComponentsController != null) { 322 // Make sure we don't divide by 0, and have at least a small runway. 323 float start = Math.min(mAtomicComponentsStartProgress, 0.9f); 324 mAtomicComponentsController.setPlayFraction((fraction - start) / (1 - start)); 325 } 326 maybeUpdateAtomicAnim(mFromState, mToState, fraction); 327 } 328 329 /** 330 * When going between normal and overview states, see if we passed the overview threshold and 331 * play the appropriate atomic animation if so. 332 */ maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState, float progress)333 private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState, 334 float progress) { 335 if (!goingBetweenNormalAndOverview(fromState, toState)) { 336 return; 337 } 338 float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD 339 : 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD; 340 boolean passedThreshold = progress >= threshold; 341 if (passedThreshold != mPassedOverviewAtomicThreshold) { 342 LauncherState atomicFromState = passedThreshold ? fromState: toState; 343 LauncherState atomicToState = passedThreshold ? toState : fromState; 344 mPassedOverviewAtomicThreshold = passedThreshold; 345 if (mAtomicAnim != null) { 346 mAtomicAnim.cancel(); 347 } 348 mAtomicAnim = createAtomicAnimForState(atomicFromState, atomicToState, ATOMIC_DURATION); 349 mAtomicAnim.addListener(new AnimationSuccessListener() { 350 @Override 351 public void onAnimationEnd(Animator animation) { 352 super.onAnimationEnd(animation); 353 mAtomicAnim = null; 354 mScheduleResumeAtomicComponent = false; 355 } 356 357 @Override 358 public void onAnimationSuccess(Animator animator) { 359 if (!mScheduleResumeAtomicComponent) { 360 return; 361 } 362 cancelAtomicComponentsController(); 363 364 if (mCurrentAnimation != null) { 365 mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction(); 366 long duration = (long) (getShiftRange() * 2); 367 mAtomicComponentsController = AnimatorPlaybackController.wrap( 368 createAtomicAnimForState(mFromState, mToState, duration), duration); 369 mAtomicComponentsController.dispatchOnStart(); 370 mAtomicComponentsTargetState = mToState; 371 maybeAutoPlayAtomicComponentsAnim(); 372 } 373 } 374 }); 375 mAtomicAnim.start(); 376 mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 377 } 378 } 379 createAtomicAnimForState(LauncherState fromState, LauncherState targetState, long duration)380 private AnimatorSet createAtomicAnimForState(LauncherState fromState, LauncherState targetState, 381 long duration) { 382 StateAnimationConfig config = getConfigForStates(fromState, targetState); 383 config.animFlags = PLAY_ATOMIC_OVERVIEW_SCALE; 384 config.duration = duration; 385 return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, config); 386 } 387 388 /** 389 * Returns animation config for state transition between provided states 390 */ getConfigForStates( LauncherState fromState, LauncherState toState)391 protected StateAnimationConfig getConfigForStates( 392 LauncherState fromState, LauncherState toState) { 393 return new StateAnimationConfig(); 394 } 395 396 @Override onDragEnd(float velocity)397 public void onDragEnd(float velocity) { 398 boolean fling = mDetector.isFling(velocity); 399 final int logAction = fling ? Touch.FLING : Touch.SWIPE; 400 401 boolean blockedFling = fling && mFlingBlockCheck.isBlocked(); 402 if (blockedFling) { 403 fling = false; 404 } 405 406 final LauncherState targetState; 407 final float progress = mCurrentAnimation.getProgressFraction(); 408 final float progressVelocity = velocity * mProgressMultiplier; 409 final float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress(); 410 if (fling) { 411 targetState = 412 Float.compare(Math.signum(velocity), Math.signum(mProgressMultiplier)) == 0 413 ? mToState : mFromState; 414 // snap to top or bottom using the release velocity 415 } else { 416 float successProgress = mToState == ALL_APPS 417 ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS; 418 targetState = (interpolatedProgress > successProgress) ? mToState : mFromState; 419 } 420 421 final float endProgress; 422 final float startProgress; 423 final long duration; 424 // Increase the duration if we prevented the fling, as we are going against a high velocity. 425 final int durationMultiplier = blockedFling && targetState == mFromState 426 ? LauncherAnimUtils.blockedFlingDurationFactor(velocity) : 1; 427 428 if (targetState == mToState) { 429 endProgress = 1; 430 if (progress >= 1) { 431 duration = 0; 432 startProgress = 1; 433 } else { 434 startProgress = Utilities.boundToRange(progress 435 + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f); 436 duration = BaseSwipeDetector.calculateDuration(velocity, 437 endProgress - Math.max(progress, 0)) * durationMultiplier; 438 } 439 } else { 440 // Let the state manager know that the animation didn't go to the target state, 441 // but don't cancel ourselves (we already clean up when the animation completes). 442 mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(); 443 444 endProgress = 0; 445 if (progress <= 0) { 446 duration = 0; 447 startProgress = 0; 448 } else { 449 startProgress = Utilities.boundToRange(progress 450 + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f); 451 duration = BaseSwipeDetector.calculateDuration(velocity, 452 Math.min(progress, 1) - endProgress) * durationMultiplier; 453 } 454 } 455 456 mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction)); 457 ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); 458 anim.setFloatValues(startProgress, endProgress); 459 maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f); 460 updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()), 461 targetState, velocity, fling); 462 mCurrentAnimation.dispatchOnStart(); 463 if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) { 464 mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity); 465 } 466 anim.start(); 467 mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, anim.getDuration()); 468 maybeAutoPlayAtomicComponentsAnim(); 469 } 470 471 /** 472 * Animates the atomic components from the current progress to the final progress. 473 * 474 * Note that this only applies when we are controlling the atomic components separately from 475 * the non-atomic components, which only happens if we reinit before the atomic animation 476 * finishes. 477 */ maybeAutoPlayAtomicComponentsAnim()478 private void maybeAutoPlayAtomicComponentsAnim() { 479 if (mAtomicComponentsController == null || mAtomicAnimAutoPlayInfo == null) { 480 return; 481 } 482 483 final AnimatorPlaybackController controller = mAtomicComponentsController; 484 ValueAnimator atomicAnim = controller.getAnimationPlayer(); 485 atomicAnim.setFloatValues(controller.getProgressFraction(), 486 mAtomicAnimAutoPlayInfo.toProgress); 487 long duration = mAtomicAnimAutoPlayInfo.endTime - SystemClock.elapsedRealtime(); 488 mAtomicAnimAutoPlayInfo = null; 489 if (duration <= 0) { 490 atomicAnim.start(); 491 atomicAnim.end(); 492 mAtomicComponentsController = null; 493 } else { 494 atomicAnim.setDuration(duration); 495 atomicAnim.addListener(new AnimatorListenerAdapter() { 496 @Override 497 public void onAnimationEnd(Animator animation) { 498 if (mAtomicComponentsController == controller) { 499 mAtomicComponentsController = null; 500 } 501 } 502 }); 503 atomicAnim.start(); 504 } 505 } 506 getRemainingAtomicDuration()507 private long getRemainingAtomicDuration() { 508 if (mAtomicAnim == null) { 509 return 0; 510 } 511 if (Utilities.ATLEAST_OREO) { 512 return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime(); 513 } else { 514 long remainingDuration = 0; 515 for (Animator anim : mAtomicAnim.getChildAnimations()) { 516 remainingDuration = Math.max(remainingDuration, anim.getDuration()); 517 } 518 return remainingDuration; 519 } 520 } 521 updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, LauncherState targetState, float velocity, boolean isFling)522 protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, 523 LauncherState targetState, float velocity, boolean isFling) { 524 animator.setDuration(expectedDuration) 525 .setInterpolator(scrollInterpolatorForVelocity(velocity)); 526 } 527 getDirectionForLog()528 protected int getDirectionForLog() { 529 return mToState.ordinal > mFromState.ordinal ? Direction.UP : Direction.DOWN; 530 } 531 onSwipeInteractionCompleted(LauncherState targetState, int logAction)532 protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { 533 if (mAtomicComponentsController != null) { 534 mAtomicComponentsController.getAnimationPlayer().end(); 535 mAtomicComponentsController = null; 536 } 537 clearState(); 538 boolean shouldGoToTargetState = true; 539 if (mPendingAnimation != null) { 540 boolean reachedTarget = mToState == targetState; 541 mPendingAnimation.finish(reachedTarget, logAction); 542 mPendingAnimation = null; 543 shouldGoToTargetState = !reachedTarget; 544 } 545 if (shouldGoToTargetState) { 546 goToTargetState(targetState, logAction); 547 } 548 } 549 goToTargetState(LauncherState targetState, int logAction)550 protected void goToTargetState(LauncherState targetState, int logAction) { 551 if (targetState != mStartState) { 552 logReachedState(logAction, targetState); 553 } 554 if (!mLauncher.isInState(targetState)) { 555 // If we're already in the target state, don't jump to it at the end of the animation in 556 // case the user started interacting with it before the animation finished. 557 mLauncher.getStateManager().goToState(targetState, false /* animated */); 558 } 559 mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start(); 560 } 561 logReachedState(int logAction, LauncherState targetState)562 private void logReachedState(int logAction, LauncherState targetState) { 563 // Transition complete. log the action 564 mLauncher.getUserEventDispatcher().logStateChangeAction(logAction, 565 getDirectionForLog(), mDetector.getDownX(), mDetector.getDownY(), 566 mStartContainerType /* e.g., hotseat */, 567 mStartState.containerType /* e.g., workspace */, 568 targetState.containerType, 569 mLauncher.getWorkspace().getCurrentPage()); 570 mLauncher.getStatsLogManager().logger() 571 .withSrcState(StatsLogManager.containerTypeToAtomState(mStartState.containerType)) 572 .withDstState(StatsLogManager.containerTypeToAtomState(targetState.containerType)) 573 .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder() 574 .setWorkspace( 575 LauncherAtom.WorkspaceContainer.newBuilder() 576 .setPageIndex(mLauncher.getWorkspace().getCurrentPage())) 577 .build()) 578 .log(StatsLogManager.getLauncherAtomEvent(mStartState.containerType, 579 targetState.containerType, mToState.ordinal > mFromState.ordinal 580 ? LAUNCHER_UNKNOWN_SWIPEUP 581 : LAUNCHER_UNKNOWN_SWIPEDOWN)); 582 } 583 clearState()584 protected void clearState() { 585 cancelAnimationControllers(); 586 if (mAtomicAnim != null) { 587 mAtomicAnim.cancel(); 588 mAtomicAnim = null; 589 } 590 mScheduleResumeAtomicComponent = false; 591 mDetector.finishedScrolling(); 592 mDetector.setDetectableScrollConditions(0, false); 593 } 594 cancelAnimationControllers()595 private void cancelAnimationControllers() { 596 mCurrentAnimation = null; 597 cancelAtomicComponentsController(); 598 } 599 cancelAtomicComponentsController()600 private void cancelAtomicComponentsController() { 601 if (mAtomicComponentsController != null) { 602 mAtomicComponentsController.getAnimationPlayer().cancel(); 603 mAtomicComponentsController = null; 604 } 605 mAtomicAnimAutoPlayInfo = null; 606 } 607 608 private static class AutoPlayAtomicAnimationInfo { 609 610 public final float toProgress; 611 public final long endTime; 612 AutoPlayAtomicAnimationInfo(float toProgress, long duration)613 AutoPlayAtomicAnimationInfo(float toProgress, long duration) { 614 this.toProgress = toProgress; 615 this.endTime = duration + SystemClock.elapsedRealtime(); 616 } 617 } 618 } 619