• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.android.launcher3.statemanager;
18 
19 import static android.animation.ValueAnimator.areAnimatorsEnabled;
20 
21 import static com.android.launcher3.Flags.enableStateManagerProtoLog;
22 import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively;
23 import static com.android.launcher3.states.StateAnimationConfig.HANDLE_STATE_APPLY;
24 import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
25 
26 import android.animation.Animator;
27 import android.animation.Animator.AnimatorListener;
28 import android.animation.AnimatorListenerAdapter;
29 import android.animation.AnimatorSet;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.util.Log;
33 
34 import androidx.annotation.FloatRange;
35 
36 import com.android.launcher3.anim.AnimationSuccessListener;
37 import com.android.launcher3.anim.AnimatorPlaybackController;
38 import com.android.launcher3.anim.PendingAnimation;
39 import com.android.launcher3.states.StateAnimationConfig;
40 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
41 import com.android.launcher3.states.StateAnimationConfig.AnimationPropertyFlags;
42 import com.android.launcher3.util.StateManagerProtoLogProxy;
43 
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.stream.Collectors;
48 
49 /**
50  * Class to manage transitions between different states for a StatefulActivity based on different
51  * states
52  * @param <S> Basestate used by the state manager
53  * @param <T> container object used to manage state
54  */
55 public class StateManager<S extends BaseState<S>, T extends StatefulContainer<S>> {
56 
57     public static final String TAG = "StateManager";
58     // b/279059025, b/325463989
59     private static final boolean DEBUG = true;
60 
61     private final AnimationState<S> mConfig = new AnimationState<>();
62     private final Handler mUiHandler;
63     private final T mContainer;
64     private final ArrayList<StateListener<S>> mListeners = new ArrayList<>();
65     private final S mBaseState;
66 
67     // Animators which are run on properties also controlled by state animations.
68     private final AtomicAnimationFactory<S> mAtomicAnimationFactory;
69 
70     private StateHandler<S>[] mStateHandlers;
71     private S mState;
72 
73     private S mLastStableState;
74     private S mCurrentStableState;
75 
76     private S mRestState;
77 
StateManager(T container, S baseState)78     public StateManager(T container, S baseState) {
79         mUiHandler = new Handler(Looper.getMainLooper());
80         mContainer = container;
81         mBaseState = baseState;
82         mState = mLastStableState = mCurrentStableState = baseState;
83         mAtomicAnimationFactory = container.createAtomicAnimationFactory();
84     }
85 
getState()86     public S getState() {
87         return mState;
88     }
89 
getTargetState()90     public S getTargetState() {
91         return mConfig.targetState;
92     }
93 
getCurrentStableState()94     public S getCurrentStableState() {
95         return mCurrentStableState;
96     }
97 
98     @Override
toString()99     public String toString() {
100         return " StateManager(mLastStableState:" + mLastStableState
101                 + ", mCurrentStableState:" + mCurrentStableState
102                 + ", mState:" + mState
103                 + ", mRestState:" + mRestState
104                 + ", isInTransition:" + isInTransition() + ")";
105     }
106 
dump(String prefix, PrintWriter writer)107     public void dump(String prefix, PrintWriter writer) {
108         writer.println(prefix + "StateManager:");
109         writer.println(prefix + "\tmLastStableState:" + mLastStableState);
110         writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState);
111         writer.println(prefix + "\tmState:" + mState);
112         writer.println(prefix + "\tmRestState:" + mRestState);
113         writer.println(prefix + "\tisInTransition:" + isInTransition());
114     }
115 
getStateHandlers()116     public StateHandler<S>[] getStateHandlers() {
117         if (mStateHandlers == null) {
118             ArrayList<StateHandler<S>> handlers = new ArrayList<>();
119             mContainer.collectStateHandlers(handlers);
120             mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
121         }
122         return mStateHandlers;
123     }
124 
addStateListener(StateListener<S> listener)125     public void addStateListener(StateListener<S> listener) {
126         mListeners.add(listener);
127     }
128 
removeStateListener(StateListener<S> listener)129     public void removeStateListener(StateListener<S> listener) {
130         mListeners.remove(listener);
131     }
132 
133     /**
134      * Returns true if the state changes should be animated.
135      */
shouldAnimateStateChange()136     public boolean shouldAnimateStateChange() {
137         return mContainer.shouldAnimateStateChange();
138     }
139 
140     /**
141      * @return {@code true} if the state matches the current state and there is no active
142      *         transition to different state.
143      */
isInStableState(S state)144     public boolean isInStableState(S state) {
145         return mState == state && mCurrentStableState == state
146                 && (mConfig.targetState == null || mConfig.targetState == state);
147     }
148 
149     /**
150      * @return {@code true} If there is an active transition.
151      */
isInTransition()152     public boolean isInTransition() {
153         return mConfig.currentAnimation != null;
154     }
155 
156     /**
157      * @see #goToState(S, boolean, AnimatorListener)
158      */
goToState(S state)159     public void goToState(S state) {
160         goToState(state, shouldAnimateStateChange());
161     }
162 
163     /**
164      * @see #goToState(S, boolean, AnimatorListener)
165      */
goToState(S state, AnimatorListener listener)166     public void goToState(S state, AnimatorListener listener) {
167         goToState(state, shouldAnimateStateChange(), listener);
168     }
169 
170     /**
171      * @see #goToState(S, boolean, AnimatorListener)
172      */
goToState(S state, boolean animated)173     public void goToState(S state, boolean animated) {
174         goToState(state, animated, 0, null);
175     }
176 
177     /**
178      * Changes the Launcher state to the provided state.
179      *
180      * @param animated false if the state should change immediately without any animation,
181      *                true otherwise
182      * @param listener any action to perform at the end of the transition, or null.
183      */
goToState(S state, boolean animated, AnimatorListener listener)184     public void goToState(S state, boolean animated, AnimatorListener listener) {
185         goToState(state, animated, 0, listener);
186     }
187 
188     /**
189      * Changes the Launcher state to the provided state after the given delay.
190      */
goToState(S state, long delay, AnimatorListener listener)191     public void goToState(S state, long delay, AnimatorListener listener) {
192         goToState(state, true, delay, listener);
193     }
194 
195     /**
196      * Changes the Launcher state to the provided state after the given delay.
197      */
goToState(S state, long delay)198     public void goToState(S state, long delay) {
199         goToState(state, true, delay, null);
200     }
201 
reapplyState()202     public void reapplyState() {
203         reapplyState(false);
204     }
205 
reapplyState(boolean cancelCurrentAnimation)206     public void reapplyState(boolean cancelCurrentAnimation) {
207         boolean wasInAnimation = mConfig.currentAnimation != null;
208         if (cancelCurrentAnimation && (mConfig.animProps & HANDLE_STATE_APPLY) == 0) {
209             // Animation canceling can trigger a cleanup routine, causing problems when we are in a
210             // launcher state that relies on member variable data. So if we are in one of those
211             // states, accelerate the current animation to its end point rather than canceling it
212             // outright.
213             if (mState.shouldPreserveDataStateOnReapply() && mConfig.currentAnimation != null) {
214                 mConfig.currentAnimation.end();
215             }
216             mAtomicAnimationFactory.cancelAllStateElementAnimation();
217             cancelAnimation();
218         }
219         if (mConfig.currentAnimation == null) {
220             for (StateHandler<S> handler : getStateHandlers()) {
221                 handler.setState(mState);
222             }
223             if (wasInAnimation) {
224                 onStateTransitionEnd(mState);
225             }
226         }
227     }
228 
229     /** Handles backProgress in predictive back gesture by passing it to state handlers. */
onBackProgressed( S toState, @FloatRange(from = 0.0, to = 1.0) float backProgress)230     public void onBackProgressed(
231             S toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
232         for (StateHandler<S> handler : getStateHandlers()) {
233             handler.onBackProgressed(toState, backProgress);
234         }
235     }
236 
237     /** Handles back cancelled event in predictive back gesture by passing it to state handlers. */
onBackCancelled(S toState)238     public void onBackCancelled(S toState) {
239         for (StateHandler<S> handler : getStateHandlers()) {
240             handler.onBackCancelled(toState);
241         }
242     }
243 
goToState( S state, boolean animated, long delay, AnimatorListener listener)244     private void goToState(
245             S state, boolean animated, long delay, AnimatorListener listener) {
246         if (enableStateManagerProtoLog()) {
247             StateManagerProtoLogProxy.logGoToState(
248                     mState, state, getTrimmedStackTrace("StateManager.goToState"));
249         } else if (DEBUG) {
250             Log.d(TAG, "goToState - fromState: " + mState + ", toState: " + state
251                     + ", partial trace:\n" + getTrimmedStackTrace("StateManager.goToState"));
252         }
253 
254         animated &= areAnimatorsEnabled();
255         if (getState() == state) {
256             if (mConfig.currentAnimation == null) {
257                 // Run any queued runnable
258                 if (listener != null) {
259                     listener.onAnimationEnd(new AnimatorSet());
260                 }
261                 return;
262             } else if ((!mConfig.isUserControlled() && animated && mConfig.targetState == state)
263                     || mState.shouldPreserveDataStateOnReapply()) {
264                 // We are running the same animation as requested, and/or target state should not be
265                 // reset -- allow the current animation to complete instead of canceling it.
266                 if (listener != null) {
267                     mConfig.currentAnimation.addListener(listener);
268                 }
269                 return;
270             }
271         }
272 
273         // Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
274         S fromState = mState;
275         cancelAnimation();
276 
277         if (!animated) {
278             mAtomicAnimationFactory.cancelAllStateElementAnimation();
279             onStateTransitionStart(state);
280             for (StateHandler<S> handler : getStateHandlers()) {
281                 handler.setState(state);
282             }
283 
284             onStateTransitionEnd(state);
285 
286             // Run any queued runnable
287             if (listener != null) {
288                 listener.onAnimationEnd(new AnimatorSet());
289             }
290             return;
291         }
292 
293         if (delay > 0) {
294             // Create the animation after the delay as some properties can change between preparing
295             // the animation and running the animation.
296             int startChangeId = mConfig.changeId;
297             mUiHandler.postDelayed(() -> {
298                 if (mConfig.changeId == startChangeId) {
299                     goToStateAnimated(state, fromState, listener);
300                 }
301             }, delay);
302         } else {
303             goToStateAnimated(state, fromState, listener);
304         }
305     }
306 
goToStateAnimated(S state, S fromState, AnimatorListener listener)307     private void goToStateAnimated(S state, S fromState,
308             AnimatorListener listener) {
309         // Since state mBaseState can be reached from multiple states, just assume that the
310         // transition plays in reverse and use the same duration as previous state.
311         mConfig.duration = state == mBaseState
312                 ? fromState.getTransitionDuration(mContainer, false /* isToState */)
313                 : state.getTransitionDuration(mContainer, true /* isToState */);
314         prepareForAtomicAnimation(fromState, state, mConfig);
315         AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
316         if (listener != null) {
317             animation.addListener(listener);
318         }
319         mUiHandler.post(new StartAnimRunnable(animation));
320     }
321 
322     /**
323      * Prepares for a non-user controlled animation from fromState to toState. Preparations include:
324      * - Setting interpolators for various animations included in the state transition.
325      * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
326      */
prepareForAtomicAnimation(S fromState, S toState, StateAnimationConfig config)327     public void prepareForAtomicAnimation(S fromState, S toState,
328             StateAnimationConfig config) {
329         mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config);
330     }
331 
332     /**
333      * Creates an animation representing atomic transitions between the provided states
334      */
createAtomicAnimation( S fromState, S toState, StateAnimationConfig config)335     public AnimatorSet createAtomicAnimation(
336             S fromState, S toState, StateAnimationConfig config) {
337         if (enableStateManagerProtoLog()) {
338             StateManagerProtoLogProxy.logCreateAtomicAnimation(
339                     mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
340         } else if (DEBUG) {
341             Log.d(TAG, "createAtomicAnimation - fromState: " + fromState + ", toState: " + toState
342                     + ", partial trace:\n" + getTrimmedStackTrace(
343                             "StateManager.createAtomicAnimation"));
344         }
345 
346         PendingAnimation builder = new PendingAnimation(config.duration);
347         prepareForAtomicAnimation(fromState, toState, config);
348 
349         for (StateHandler<S> handler : getStateHandlers()) {
350             handler.setStateWithAnimation(toState, config, builder);
351         }
352         return builder.buildAnim();
353     }
354 
355     /**
356      * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
357      * state transition.
358      * @param state the final state for the transition.
359      * @param duration intended duration for state playback. Use higher duration for better
360      *                accuracy.
361      */
createAnimationToNewWorkspace( S state, long duration)362     public AnimatorPlaybackController createAnimationToNewWorkspace(
363             S state, long duration) {
364         return createAnimationToNewWorkspace(state, duration, 0 /* animFlags */);
365     }
366 
createAnimationToNewWorkspace( S state, long duration, @AnimationFlags int animFlags)367     public AnimatorPlaybackController createAnimationToNewWorkspace(
368             S state, long duration, @AnimationFlags int animFlags) {
369         StateAnimationConfig config = new StateAnimationConfig();
370         config.duration = duration;
371         config.animFlags = animFlags;
372         return createAnimationToNewWorkspace(state, config);
373     }
374 
createAnimationToNewWorkspace(S state, StateAnimationConfig config)375     public AnimatorPlaybackController createAnimationToNewWorkspace(S state,
376             StateAnimationConfig config) {
377         config.animProps |= StateAnimationConfig.USER_CONTROLLED;
378         cancelAnimation();
379         config.copyTo(mConfig);
380         mConfig.playbackController = createAnimationToNewWorkspaceInternal(state)
381                 .createPlaybackController();
382         return mConfig.playbackController;
383     }
384 
createAnimationToNewWorkspaceInternal(final S state)385     private PendingAnimation createAnimationToNewWorkspaceInternal(final S state) {
386         PendingAnimation builder = new PendingAnimation(mConfig.duration);
387         if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) {
388             for (StateHandler<S> handler : getStateHandlers()) {
389                 handler.setStateWithAnimation(state, mConfig, builder);
390             }
391         }
392         builder.addListener(createStateAnimationListener(state));
393         mConfig.setAnimation(builder.buildAnim(), state);
394         return builder;
395     }
396 
createStateAnimationListener(S state)397     private AnimatorListener createStateAnimationListener(S state) {
398         return new AnimationSuccessListener() {
399 
400             @Override
401             public void onAnimationStart(Animator animation) {
402                 // Change the internal state only when the transition actually starts
403                 onStateTransitionStart(state);
404             }
405 
406             @Override
407             public void onAnimationSuccess(Animator animator) {
408                 onStateTransitionEnd(state);
409             }
410         };
411     }
412 
413     private void onStateTransitionStart(S state) {
414         mState = state;
415         mContainer.onStateSetStart(mState);
416 
417         if (enableStateManagerProtoLog()) {
418             StateManagerProtoLogProxy.logOnStateTransitionStart(state);
419         } else if (DEBUG) {
420             Log.d(TAG, "onStateTransitionStart - state: " + state);
421         }
422         for (int i = mListeners.size() - 1; i >= 0; i--) {
423             mListeners.get(i).onStateTransitionStart(state);
424         }
425     }
426 
427     private void onStateTransitionEnd(S state) {
428         // Only change the stable states after the transitions have finished
429         if (state != mCurrentStableState) {
430             mLastStableState = state.getHistoryForState(mCurrentStableState);
431             mCurrentStableState = state;
432         }
433 
434         mContainer.onStateSetEnd(state);
435         if (state == mBaseState) {
436             setRestState(null);
437         }
438 
439         if (enableStateManagerProtoLog()) {
440             StateManagerProtoLogProxy.logOnStateTransitionEnd(state);
441         } else if (DEBUG) {
442             Log.d(TAG, "onStateTransitionEnd - state: " + state);
443         }
444         for (int i = mListeners.size() - 1; i >= 0; i--) {
445             mListeners.get(i).onStateTransitionComplete(state);
446         }
447     }
448 
449     public S getLastState() {
450         return mLastStableState;
451     }
452 
453     public void moveToRestState() {
454         moveToRestState(shouldAnimateStateChange());
455     }
456 
457     public void moveToRestState(boolean isAnimated) {
458         if (mConfig.currentAnimation != null && mConfig.isUserControlled()) {
459             // The user is doing something. Lets not mess it up
460             return;
461         }
462         if (mState.shouldDisableRestore()) {
463             goToState(getRestState(), isAnimated);
464             // Reset history
465             mLastStableState = mBaseState;
466         }
467     }
468 
469     public S getRestState() {
470         return mRestState == null ? mBaseState : mRestState;
471     }
472 
473     public void setRestState(S restState) {
474         mRestState = restState;
475     }
476 
477     /**
478      * Cancels the current animation.
479      */
480     public void cancelAnimation() {
481         if (enableStateManagerProtoLog()) {
482             StateManagerProtoLogProxy.logCancelAnimation(
483                     mConfig.currentAnimation != null,
484                     getTrimmedStackTrace("StateManager.cancelAnimation"));
485         } else if (DEBUG && mConfig.currentAnimation != null) {
486             Log.d(TAG, "cancelAnimation - with ongoing animation"
487                     + ", partial trace:\n" + getTrimmedStackTrace("StateManager.cancelAnimation"));
488         }
489         mConfig.reset();
490         // It could happen that a new animation is set as a result of an endListener on the
491         // existing animation.
492         while (mConfig.currentAnimation != null || mConfig.playbackController != null) {
493             mConfig.reset();
494         }
495     }
496 
497     /**
498      * Sets the provided controller as the current user controlled state animation
499      */
500     public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
501         setCurrentAnimation(controller, StateAnimationConfig.USER_CONTROLLED);
502     }
503 
504     public void setCurrentAnimation(AnimatorPlaybackController controller,
505             @AnimationPropertyFlags int animationProps) {
506         clearCurrentAnimation();
507         setCurrentAnimation(controller.getTarget());
508         mConfig.animProps = animationProps;
509         mConfig.playbackController = controller;
510     }
511 
512     /**
513      * @see #setCurrentAnimation(AnimatorSet, Animator...). Using this method tells the StateManager
514      * that this is a custom animation to the given state, and thus the StateManager will add an
515      * animation listener to call {@link #onStateTransitionStart} and {@link #onStateTransitionEnd}.
516      * @param anim The custom animation to the given state.
517      * @param toState The state we are animating towards.
518      */
519     public void setCurrentAnimation(AnimatorSet anim, S toState) {
520         cancelAnimation();
521         setCurrentAnimation(anim);
522         anim.addListener(createStateAnimationListener(toState));
523     }
524 
525     /**
526      * Sets the animation as the current state animation, i.e., canceled when
527      * starting another animation and may block some launcher interactions while running.
528      *
529      * @param childAnimations Set of animations with the new target is controlling.
530      */
531     public void setCurrentAnimation(AnimatorSet anim, Animator... childAnimations) {
532         for (Animator childAnim : childAnimations) {
533             if (childAnim == null) {
534                 continue;
535             }
536             if (mConfig.playbackController != null
537                     && mConfig.playbackController.getTarget() == childAnim) {
538                 clearCurrentAnimation();
539                 break;
540             } else if (mConfig.currentAnimation == childAnim) {
541                 clearCurrentAnimation();
542                 break;
543             }
544         }
545         boolean reapplyNeeded = mConfig.currentAnimation != null;
546         cancelAnimation();
547         if (reapplyNeeded) {
548             reapplyState();
549             // Dispatch on transition end, so that any transient property is cleared.
550             onStateTransitionEnd(mState);
551         }
552         mConfig.setAnimation(anim, null);
553     }
554 
555     /**
556      * Cancels a currently running gesture animation
557      */
558     public void cancelStateElementAnimation(int index) {
559         if (mAtomicAnimationFactory.mStateElementAnimators[index] != null) {
560             mAtomicAnimationFactory.mStateElementAnimators[index].cancel();
561         }
562     }
563 
564     public Animator createStateElementAnimation(int index, float... values) {
565         cancelStateElementAnimation(index);
566         Animator anim = mAtomicAnimationFactory.createStateElementAnimation(index, values);
567         mAtomicAnimationFactory.mStateElementAnimators[index] = anim;
568         anim.addListener(new AnimatorListenerAdapter() {
569             @Override
570             public void onAnimationEnd(Animator animation) {
571                 mAtomicAnimationFactory.mStateElementAnimators[index] = null;
572             }
573         });
574         return anim;
575     }
576 
577     private void clearCurrentAnimation() {
578         if (mConfig.currentAnimation != null) {
579             mConfig.currentAnimation.removeListener(mConfig);
580             mConfig.currentAnimation = null;
581         }
582         mConfig.playbackController = null;
583     }
584 
585     private String getTrimmedStackTrace(String callingMethodName) {
586         String stackTrace = Log.getStackTraceString(new Exception());
587         return Arrays.stream(stackTrace.split("\\n"))
588                 .skip(2) // Removes the line "java.lang.Exception" and "getTrimmedStackTrace".
589                 .filter(traceLine -> !traceLine.contains(callingMethodName))
590                 .limit(3)
591                 .collect(Collectors.joining("\n"));
592     }
593 
594     private class StartAnimRunnable implements Runnable {
595 
596         private final AnimatorSet mAnim;
597 
598         public StartAnimRunnable(AnimatorSet anim) {
599             mAnim = anim;
600         }
601 
602         @Override
603         public void run() {
604             if (mConfig.currentAnimation != mAnim) {
605                 return;
606             }
607             mAnim.start();
608         }
609     }
610 
611     private static class AnimationState<STATE_TYPE> extends StateAnimationConfig
612             implements AnimatorListener {
613 
614         private static final StateAnimationConfig DEFAULT = new StateAnimationConfig();
615 
616         public AnimatorPlaybackController playbackController;
617         public AnimatorSet currentAnimation;
618         public STATE_TYPE targetState;
619 
620         // Id to keep track of config changes, to tie an animation with the corresponding request
621         public int changeId = 0;
622 
623         /**
624          * Cancels the current animation and resets config variables.
625          */
626         public void reset() {
627             AnimatorSet anim = currentAnimation;
628             AnimatorPlaybackController pc = playbackController;
629 
630             DEFAULT.copyTo(this);
631             targetState = null;
632             currentAnimation = null;
633             playbackController = null;
634             changeId++;
635 
636             if (pc != null) {
637                 pc.getAnimationPlayer().cancel();
638                 pc.dispatchOnCancel().dispatchOnEnd();
639             } else if (anim != null) {
640                 anim.setDuration(0);
641                 if (!anim.isStarted()) {
642                     // If the animation is not started the listeners do not get notified,
643                     // notify manually.
644                     callListenerCommandRecursively(anim, AnimatorListener::onAnimationCancel);
645                     callListenerCommandRecursively(anim, AnimatorListener::onAnimationEnd);
646                 }
647                 anim.cancel();
648             }
649         }
650 
651         @Override
652         public void onAnimationEnd(Animator animation) {
653             if (playbackController != null && playbackController.getTarget() == animation) {
654                 playbackController = null;
655             }
656             if (currentAnimation == animation) {
657                 currentAnimation = null;
658             }
659         }
660 
661         public void setAnimation(AnimatorSet animation, STATE_TYPE targetState) {
662             currentAnimation = animation;
663             this.targetState = targetState;
664             currentAnimation.addListener(this);
665         }
666 
667         @Override
668         public void onAnimationStart(Animator animator) { }
669 
670         @Override
671         public void onAnimationCancel(Animator animator) { }
672 
673         @Override
674         public void onAnimationRepeat(Animator animator) { }
675     }
676 
677     public interface StateHandler<STATE_TYPE> {
678 
679         /**
680          * Updates the UI to {@param state} without any animations
681          */
682         void setState(STATE_TYPE state);
683 
684         /**
685          * Sets the UI to {@param state} by animating any changes.
686          */
687         void setStateWithAnimation(
688                 STATE_TYPE toState, StateAnimationConfig config, PendingAnimation animation);
689 
690         /** Handles backProgress in predictive back gesture for target state. */
691         default void onBackProgressed(
692                 STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {}
693 
694         /** Handles back cancelled event in predictive back gesture for target state.  */
695         default void onBackCancelled(STATE_TYPE toState) {}
696     }
697 
698     public interface StateListener<STATE_TYPE> {
699 
700         default void onStateTransitionStart(STATE_TYPE toState) { }
701 
702         default void onStateTransitionComplete(STATE_TYPE finalState) { }
703     }
704 
705     /**
706      * Factory class to configure and create atomic animations.
707      */
708     public static class AtomicAnimationFactory<STATE_TYPE> {
709 
710         protected static final int NEXT_INDEX = 0;
711 
712         private final Animator[] mStateElementAnimators;
713 
714         /**
715          *
716          * @param sharedElementAnimCount number of animations which run on state properties
717          */
718         public AtomicAnimationFactory(int sharedElementAnimCount) {
719             mStateElementAnimators = new Animator[sharedElementAnimCount];
720         }
721 
722         void cancelAllStateElementAnimation() {
723             for (Animator animator : mStateElementAnimators) {
724                 if (animator != null) {
725                     animator.cancel();
726                 }
727             }
728         }
729 
730         /**
731          * Creates animations for elements which can be also be part of state transitions. The
732          * actual definition of the animation is up to the app to define.
733          *
734          */
735         public Animator createStateElementAnimation(int index, float... values) {
736             throw new RuntimeException("Unknown gesture animation " + index);
737         }
738 
739         /**
740          * Prepares for a non-user controlled animation from fromState to this state. Preparations
741          * include:
742          * - Setting interpolators for various animations included in the state transition.
743          * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
744          */
745         public void prepareForAtomicAnimation(
746                 STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) { }
747     }
748 }
749