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