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