• 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;
18 
19 import static android.view.View.VISIBLE;
20 import static com.android.launcher3.LauncherState.NORMAL;
21 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
22 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
23 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
24 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
25 import static com.android.launcher3.anim.Interpolators.ACCEL;
26 import static com.android.launcher3.anim.Interpolators.DEACCEL;
27 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
28 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
29 import static com.android.launcher3.anim.Interpolators.clampToProgress;
30 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
31 
32 import android.animation.Animator;
33 import android.animation.AnimatorListenerAdapter;
34 import android.animation.AnimatorSet;
35 import android.os.Handler;
36 import android.os.Looper;
37 import android.support.annotation.IntDef;
38 
39 import com.android.launcher3.anim.AnimationSuccessListener;
40 import com.android.launcher3.anim.AnimatorPlaybackController;
41 import com.android.launcher3.anim.AnimatorSetBuilder;
42 import com.android.launcher3.anim.PropertySetter;
43 import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
44 import com.android.launcher3.uioverrides.UiFactory;
45 
46 import java.lang.annotation.Retention;
47 import java.lang.annotation.RetentionPolicy;
48 import java.util.ArrayList;
49 
50 /**
51  * TODO: figure out what kind of tests we can write for this
52  *
53  * Things to test when changing the following class.
54  *   - Home from workspace
55  *          - from center screen
56  *          - from other screens
57  *   - Home from all apps
58  *          - from center screen
59  *          - from other screens
60  *   - Back from all apps
61  *          - from center screen
62  *          - from other screens
63  *   - Launch app from workspace and quit
64  *          - with back
65  *          - with home
66  *   - Launch app from all apps and quit
67  *          - with back
68  *          - with home
69  *   - Go to a screen that's not the default, then all
70  *     apps, and launch and app, and go back
71  *          - with back
72  *          -with home
73  *   - On workspace, long press power and go back
74  *          - with back
75  *          - with home
76  *   - On all apps, long press power and go back
77  *          - with back
78  *          - with home
79  *   - On workspace, power off
80  *   - On all apps, power off
81  *   - Launch an app and turn off the screen while in that app
82  *          - Go back with home key
83  *          - Go back with back key  TODO: make this not go to workspace
84  *          - From all apps
85  *          - From workspace
86  *   - Enter and exit car mode (becase it causes an extra configuration changed)
87  *          - From all apps
88  *          - From the center workspace
89  *          - From another workspace
90  */
91 public class LauncherStateManager {
92 
93     public static final String TAG = "StateManager";
94 
95     // We separate the state animations into "atomic" and "non-atomic" components. The atomic
96     // components may be run atomically - that is, all at once, instead of user-controlled. However,
97     // atomic components are not restricted to this purpose; they can be user-controlled alongside
98     // non atomic components as well.
99     @IntDef(flag = true, value = {
100             NON_ATOMIC_COMPONENT,
101             ATOMIC_COMPONENT
102     })
103     @Retention(RetentionPolicy.SOURCE)
104     public @interface AnimationComponents {}
105     public static final int NON_ATOMIC_COMPONENT = 1 << 0;
106     public static final int ATOMIC_COMPONENT = 1 << 1;
107 
108     public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_COMPONENT;
109 
110     private final AnimationConfig mConfig = new AnimationConfig();
111     private final Handler mUiHandler;
112     private final Launcher mLauncher;
113     private final ArrayList<StateListener> mListeners = new ArrayList<>();
114 
115     private StateHandler[] mStateHandlers;
116     private LauncherState mState = NORMAL;
117 
118     private LauncherState mLastStableState = NORMAL;
119     private LauncherState mCurrentStableState = NORMAL;
120 
121     private LauncherState mRestState;
122 
LauncherStateManager(Launcher l)123     public LauncherStateManager(Launcher l) {
124         mUiHandler = new Handler(Looper.getMainLooper());
125         mLauncher = l;
126     }
127 
getState()128     public LauncherState getState() {
129         return mState;
130     }
131 
getStateHandlers()132     public StateHandler[] getStateHandlers() {
133         if (mStateHandlers == null) {
134             mStateHandlers = UiFactory.getStateHandler(mLauncher);
135         }
136         return mStateHandlers;
137     }
138 
addStateListener(StateListener listener)139     public void addStateListener(StateListener listener) {
140         mListeners.add(listener);
141     }
142 
removeStateListener(StateListener listener)143     public void removeStateListener(StateListener listener) {
144         mListeners.remove(listener);
145     }
146 
147     /**
148      * @see #goToState(LauncherState, boolean, Runnable)
149      */
goToState(LauncherState state)150     public void goToState(LauncherState state) {
151         goToState(state, !mLauncher.isForceInvisible() && mLauncher.isStarted() /* animated */);
152     }
153 
154     /**
155      * @see #goToState(LauncherState, boolean, Runnable)
156      */
goToState(LauncherState state, boolean animated)157     public void goToState(LauncherState 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(LauncherState state, boolean animated, Runnable onCompleteRunnable)168     public void goToState(LauncherState state, boolean animated, Runnable onCompleteRunnable) {
169         goToState(state, animated, 0, onCompleteRunnable);
170     }
171 
172     /**
173      * Changes the Launcher state to the provided state after the given delay.
174      */
goToState(LauncherState state, long delay, Runnable onCompleteRunnable)175     public void goToState(LauncherState state, long delay, Runnable onCompleteRunnable) {
176         goToState(state, true, delay, onCompleteRunnable);
177     }
178 
179     /**
180      * Changes the Launcher state to the provided state after the given delay.
181      */
goToState(LauncherState state, long delay)182     public void goToState(LauncherState 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         if (cancelCurrentAnimation) {
192             cancelAnimation();
193         }
194         if (mConfig.mCurrentAnimation == null) {
195             for (StateHandler handler : getStateHandlers()) {
196                 handler.setState(mState);
197             }
198         }
199     }
200 
goToState(LauncherState state, boolean animated, long delay, final Runnable onCompleteRunnable)201     private void goToState(LauncherState state, boolean animated, long delay,
202             final Runnable onCompleteRunnable) {
203         if (mLauncher.isInState(state)) {
204             if (mConfig.mCurrentAnimation == null) {
205                 // Run any queued runnable
206                 if (onCompleteRunnable != null) {
207                     onCompleteRunnable.run();
208                 }
209                 return;
210             } else if (!mConfig.userControlled && animated && mConfig.mTargetState == state) {
211                 // We are running the same animation as requested
212                 if (onCompleteRunnable != null) {
213                     mConfig.mCurrentAnimation.addListener(new AnimationSuccessListener() {
214                         @Override
215                         public void onAnimationSuccess(Animator animator) {
216                             onCompleteRunnable.run();
217                         }
218                     });
219                 }
220                 return;
221             }
222         }
223 
224         // Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
225         LauncherState fromState = mState;
226         mConfig.reset();
227 
228         if (!animated) {
229             onStateTransitionStart(state);
230             for (StateHandler handler : getStateHandlers()) {
231                 handler.setState(state);
232             }
233 
234             for (int i = mListeners.size() - 1; i >= 0; i--) {
235                 mListeners.get(i).onStateSetImmediately(state);
236             }
237             onStateTransitionEnd(state);
238 
239             // Run any queued runnable
240             if (onCompleteRunnable != null) {
241                 onCompleteRunnable.run();
242             }
243             return;
244         }
245 
246         // Since state NORMAL can be reached from multiple states, just assume that the
247         // transition plays in reverse and use the same duration as previous state.
248         mConfig.duration = state == NORMAL ? fromState.transitionDuration : state.transitionDuration;
249 
250         AnimatorSetBuilder builder = new AnimatorSetBuilder();
251         prepareForAtomicAnimation(fromState, state, builder);
252         AnimatorSet animation = createAnimationToNewWorkspaceInternal(
253                 state, builder, onCompleteRunnable);
254         Runnable runnable = new StartAnimRunnable(animation);
255         if (delay > 0) {
256             mUiHandler.postDelayed(runnable, delay);
257         } else {
258             mUiHandler.post(runnable);
259         }
260     }
261 
262     /**
263      * Prepares for a non-user controlled animation from fromState to toState. Preparations include:
264      * - Setting interpolators for various animations included in the state transition.
265      * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
266      */
prepareForAtomicAnimation(LauncherState fromState, LauncherState toState, AnimatorSetBuilder builder)267     public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
268             AnimatorSetBuilder builder) {
269         if (fromState == NORMAL && toState.overviewUi) {
270             builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
271             builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
272             builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
273             builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
274 
275             // Start from a higher overview scale, but only if we're invisible so we don't jump.
276             UiFactory.prepareToShowOverview(mLauncher);
277         } else if (fromState.overviewUi && toState == NORMAL) {
278             builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
279             builder.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
280             builder.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
281             builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
282             Workspace workspace = mLauncher.getWorkspace();
283 
284             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
285             boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
286             if (isWorkspaceVisible) {
287                 CellLayout currentChild = (CellLayout) workspace.getChildAt(
288                         workspace.getCurrentPage());
289                 isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
290                         && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
291             }
292             if (!isWorkspaceVisible) {
293                 workspace.setScaleX(0.92f);
294                 workspace.setScaleY(0.92f);
295             }
296         }
297     }
298 
299     /**
300      * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
301      * state transition.
302      * @param state the final state for the transition.
303      * @param duration intended duration for normal playback. Use higher duration for better
304      *                accuracy.
305      */
createAnimationToNewWorkspace( LauncherState state, long duration)306     public AnimatorPlaybackController createAnimationToNewWorkspace(
307             LauncherState state, long duration) {
308         return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
309     }
310 
createAnimationToNewWorkspace( LauncherState state, long duration, @AnimationComponents int animComponents)311     public AnimatorPlaybackController createAnimationToNewWorkspace(
312             LauncherState state, long duration, @AnimationComponents int animComponents) {
313         return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
314                 animComponents);
315     }
316 
createAnimationToNewWorkspace(LauncherState state, AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable, @AnimationComponents int animComponents)317     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
318             AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
319             @AnimationComponents int animComponents) {
320         mConfig.reset();
321         mConfig.userControlled = true;
322         mConfig.animComponents = animComponents;
323         mConfig.duration = duration;
324         mConfig.playbackController = AnimatorPlaybackController.wrap(
325                 createAnimationToNewWorkspaceInternal(state, builder, null), duration,
326                 onCancelRunnable);
327         return mConfig.playbackController;
328     }
329 
createAnimationToNewWorkspaceInternal(final LauncherState state, AnimatorSetBuilder builder, final Runnable onCompleteRunnable)330     protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
331             AnimatorSetBuilder builder, final Runnable onCompleteRunnable) {
332 
333         for (StateHandler handler : getStateHandlers()) {
334             builder.startTag(handler);
335             handler.setStateWithAnimation(state, builder, mConfig);
336         }
337 
338         final AnimatorSet animation = builder.build();
339         animation.addListener(new AnimationSuccessListener() {
340 
341             @Override
342             public void onAnimationStart(Animator animation) {
343                 // Change the internal state only when the transition actually starts
344                 onStateTransitionStart(state);
345                 for (int i = mListeners.size() - 1; i >= 0; i--) {
346                     mListeners.get(i).onStateTransitionStart(state);
347                 }
348             }
349 
350             @Override
351             public void onAnimationCancel(Animator animation) {
352                 super.onAnimationCancel(animation);
353                 mState = mCurrentStableState;
354             }
355 
356             @Override
357             public void onAnimationSuccess(Animator animator) {
358                 // Run any queued runnables
359                 if (onCompleteRunnable != null) {
360                     onCompleteRunnable.run();
361                 }
362                 onStateTransitionEnd(state);
363                 for (int i = mListeners.size() - 1; i >= 0; i--) {
364                     mListeners.get(i).onStateTransitionComplete(state);
365                 }
366             }
367         });
368         mConfig.setAnimation(animation, state);
369         return mConfig.mCurrentAnimation;
370     }
371 
onStateTransitionStart(LauncherState state)372     private void onStateTransitionStart(LauncherState state) {
373         mState.onStateDisabled(mLauncher);
374         mState = state;
375         mState.onStateEnabled(mLauncher);
376         mLauncher.getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
377 
378         if (state.disablePageClipping) {
379             // Only disable clipping if needed, otherwise leave it as previous value.
380             mLauncher.getWorkspace().setClipChildren(false);
381         }
382         UiFactory.onLauncherStateOrResumeChanged(mLauncher);
383     }
384 
onStateTransitionEnd(LauncherState state)385     private void onStateTransitionEnd(LauncherState state) {
386         // Only change the stable states after the transitions have finished
387         if (state != mCurrentStableState) {
388             mLastStableState = state.getHistoryForState(mCurrentStableState);
389             mCurrentStableState = state;
390         }
391 
392         state.onStateTransitionEnd(mLauncher);
393         mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping);
394         mLauncher.finishAutoCancelActionMode();
395 
396         if (state == NORMAL) {
397             setRestState(null);
398         }
399 
400         UiFactory.onLauncherStateOrResumeChanged(mLauncher);
401 
402         mLauncher.getDragLayer().requestFocus();
403     }
404 
onWindowFocusChanged()405     public void onWindowFocusChanged() {
406         UiFactory.onLauncherStateOrFocusChanged(mLauncher);
407     }
408 
getLastState()409     public LauncherState getLastState() {
410         return mLastStableState;
411     }
412 
moveToRestState()413     public void moveToRestState() {
414         if (mConfig.mCurrentAnimation != null && mConfig.userControlled) {
415             // The user is doing something. Lets not mess it up
416             return;
417         }
418         if (mState.disableRestore) {
419             goToState(getRestState());
420             // Reset history
421             mLastStableState = NORMAL;
422         }
423     }
424 
getRestState()425     public LauncherState getRestState() {
426         return mRestState == null ? NORMAL : mRestState;
427     }
428 
setRestState(LauncherState restState)429     public void setRestState(LauncherState restState) {
430         mRestState = restState;
431     }
432 
433     /**
434      * Cancels the current animation.
435      */
cancelAnimation()436     public void cancelAnimation() {
437         mConfig.reset();
438     }
439 
setCurrentUserControlledAnimation(AnimatorPlaybackController controller)440     public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
441         setCurrentAnimation(controller.getTarget());
442         mConfig.userControlled = true;
443         mConfig.playbackController = controller;
444     }
445 
446     /**
447      * Sets the animation as the current state animation, i.e., canceled when
448      * starting another animation and may block some launcher interactions while running.
449      *
450      * @param childAnimations Set of animations with the new target is controlling.
451      */
setCurrentAnimation(AnimatorSet anim, Animator... childAnimations)452     public void setCurrentAnimation(AnimatorSet anim, Animator... childAnimations) {
453         for (Animator childAnim : childAnimations) {
454             if (childAnim == null) {
455                 continue;
456             }
457             if (mConfig.playbackController != null
458                     && mConfig.playbackController.getTarget() == childAnim) {
459                 clearCurrentAnimation();
460                 break;
461             } else if (mConfig.mCurrentAnimation == childAnim) {
462                 clearCurrentAnimation();
463                 break;
464             }
465         }
466         boolean reapplyNeeded = mConfig.mCurrentAnimation != null;
467         cancelAnimation();
468         if (reapplyNeeded) {
469             reapplyState();
470         }
471         mConfig.setAnimation(anim, null);
472     }
473 
clearCurrentAnimation()474     private void clearCurrentAnimation() {
475         if (mConfig.mCurrentAnimation != null) {
476             mConfig.mCurrentAnimation.removeListener(mConfig);
477             mConfig.mCurrentAnimation = null;
478         }
479         mConfig.playbackController = null;
480     }
481 
482     private class StartAnimRunnable implements Runnable {
483 
484         private final AnimatorSet mAnim;
485 
StartAnimRunnable(AnimatorSet anim)486         public StartAnimRunnable(AnimatorSet anim) {
487             mAnim = anim;
488         }
489 
490         @Override
run()491         public void run() {
492             if (mConfig.mCurrentAnimation != mAnim) {
493                 return;
494             }
495             mAnim.start();
496         }
497     }
498 
499     public static class AnimationConfig extends AnimatorListenerAdapter {
500         public long duration;
501         public boolean userControlled;
502         public AnimatorPlaybackController playbackController;
503         public @AnimationComponents int animComponents = ANIM_ALL;
504         private PropertySetter mPropertySetter;
505 
506         private AnimatorSet mCurrentAnimation;
507         private LauncherState mTargetState;
508 
509         /**
510          * Cancels the current animation and resets config variables.
511          */
reset()512         public void reset() {
513             duration = 0;
514             userControlled = false;
515             animComponents = ANIM_ALL;
516             mPropertySetter = null;
517             mTargetState = null;
518 
519             if (playbackController != null) {
520                 playbackController.getAnimationPlayer().cancel();
521                 playbackController.dispatchOnCancel();
522             } else if (mCurrentAnimation != null) {
523                 mCurrentAnimation.setDuration(0);
524                 mCurrentAnimation.cancel();
525             }
526 
527             mCurrentAnimation = null;
528             playbackController = null;
529         }
530 
getPropertySetter(AnimatorSetBuilder builder)531         public PropertySetter getPropertySetter(AnimatorSetBuilder builder) {
532             if (mPropertySetter == null) {
533                 mPropertySetter = duration == 0 ? NO_ANIM_PROPERTY_SETTER
534                         : new AnimatedPropertySetter(duration, builder);
535             }
536             return mPropertySetter;
537         }
538 
539         @Override
onAnimationEnd(Animator animation)540         public void onAnimationEnd(Animator animation) {
541             if (mCurrentAnimation == animation) {
542                 mCurrentAnimation = null;
543             }
544         }
545 
setAnimation(AnimatorSet animation, LauncherState targetState)546         public void setAnimation(AnimatorSet animation, LauncherState targetState) {
547             mCurrentAnimation = animation;
548             mTargetState = targetState;
549             mCurrentAnimation.addListener(this);
550         }
551 
playAtomicComponent()552         public boolean playAtomicComponent() {
553             return (animComponents & ATOMIC_COMPONENT) != 0;
554         }
555 
playNonAtomicComponent()556         public boolean playNonAtomicComponent() {
557             return (animComponents & NON_ATOMIC_COMPONENT) != 0;
558         }
559     }
560 
561     public interface StateHandler {
562 
563         /**
564          * Updates the UI to {@param state} without any animations
565          */
setState(LauncherState state)566         void setState(LauncherState state);
567 
568         /**
569          * Sets the UI to {@param state} by animating any changes.
570          */
setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder, AnimationConfig config)571         void setStateWithAnimation(LauncherState toState,
572                 AnimatorSetBuilder builder, AnimationConfig config);
573     }
574 
575     public interface StateListener {
576 
577         /**
578          * Called when the state is set without an animation.
579          */
onStateSetImmediately(LauncherState state)580         void onStateSetImmediately(LauncherState state);
581 
onStateTransitionStart(LauncherState toState)582         void onStateTransitionStart(LauncherState toState);
onStateTransitionComplete(LauncherState finalState)583         void onStateTransitionComplete(LauncherState finalState);
584     }
585 }
586