blob: f016e8d2e5d80f80d16ede049d508cef4834ea70 [file] [log] [blame]
Winson Chungb745afb2015-03-02 11:51:23 -08001/*
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
17package com.android.launcher3;
18
Sunny Goyalea609262017-10-25 15:47:38 -070019import static com.android.launcher3.LauncherState.NORMAL;
20
Winson Chungb745afb2015-03-02 11:51:23 -080021import android.animation.Animator;
22import android.animation.AnimatorListenerAdapter;
23import android.animation.AnimatorSet;
Sunny Goyalbe93f262017-10-20 17:05:27 -070024import android.os.Handler;
25import android.os.Looper;
Winson Chungb745afb2015-03-02 11:51:23 -080026import android.view.View;
Adam Cohen15588932015-05-26 23:03:31 -070027
Hyunyoung Song645764e2016-06-06 14:19:02 -070028import com.android.launcher3.allapps.AllAppsTransitionController;
Sunny Goyalb5e65c82016-10-26 18:32:38 -070029import com.android.launcher3.anim.AnimationLayerSet;
Sunny Goyalbe93f262017-10-20 17:05:27 -070030import com.android.launcher3.anim.AnimationSuccessListener;
Sunny Goyal03102202017-10-27 11:05:26 -070031import com.android.launcher3.anim.AnimatorPlaybackController;
Sunny Goyalc4fa8c32017-11-07 12:23:58 -080032import com.android.launcher3.uioverrides.UiFactory;
Adam Cohen15588932015-05-26 23:03:31 -070033
Winson Chungb745afb2015-03-02 11:51:23 -080034/**
35 * TODO: figure out what kind of tests we can write for this
36 *
37 * Things to test when changing the following class.
38 * - Home from workspace
39 * - from center screen
40 * - from other screens
41 * - Home from all apps
42 * - from center screen
43 * - from other screens
44 * - Back from all apps
45 * - from center screen
46 * - from other screens
47 * - Launch app from workspace and quit
48 * - with back
49 * - with home
50 * - Launch app from all apps and quit
51 * - with back
52 * - with home
53 * - Go to a screen that's not the default, then all
54 * apps, and launch and app, and go back
55 * - with back
56 * -with home
57 * - On workspace, long press power and go back
58 * - with back
59 * - with home
60 * - On all apps, long press power and go back
61 * - with back
62 * - with home
63 * - On workspace, power off
64 * - On all apps, power off
65 * - Launch an app and turn off the screen while in that app
66 * - Go back with home key
67 * - Go back with back key TODO: make this not go to workspace
68 * - From all apps
69 * - From workspace
70 * - Enter and exit car mode (becuase it causes an extra configuration changed)
71 * - From all apps
72 * - From the center workspace
73 * - From another workspace
74 */
Sunny Goyal3e3f44c2017-10-23 17:14:52 -070075public class LauncherStateManager {
Winson Chungb745afb2015-03-02 11:51:23 -080076
Sunny Goyal3e3f44c2017-10-23 17:14:52 -070077 public static final String TAG = "StateManager";
Winson Chungb745afb2015-03-02 11:51:23 -080078
Sunny Goyalaeb16432017-10-16 11:46:41 -070079 private final AnimationConfig mConfig = new AnimationConfig();
Sunny Goyalbe93f262017-10-20 17:05:27 -070080 private final Handler mUiHandler;
81 private final Launcher mLauncher;
Winson Chungb745afb2015-03-02 11:51:23 -080082
Sunny Goyalc4fa8c32017-11-07 12:23:58 -080083 private StateHandler[] mStateHandlers;
Sunny Goyalea609262017-10-25 15:47:38 -070084 private LauncherState mState = NORMAL;
85
Sunny Goyalc4fa8c32017-11-07 12:23:58 -080086 public LauncherStateManager(Launcher l) {
Sunny Goyalbe93f262017-10-20 17:05:27 -070087 mUiHandler = new Handler(Looper.getMainLooper());
Winson Chungb745afb2015-03-02 11:51:23 -080088 mLauncher = l;
Winson Chungb745afb2015-03-02 11:51:23 -080089 }
90
Sunny Goyalea609262017-10-25 15:47:38 -070091 public LauncherState getState() {
92 return mState;
93 }
94
Sunny Goyalc4fa8c32017-11-07 12:23:58 -080095 private StateHandler[] getStateHandlers() {
96 if (mStateHandlers == null) {
97 mStateHandlers = UiFactory.getStateHandler(mLauncher);
98 }
99 return mStateHandlers;
100 }
101
Sunny Goyal3e3f44c2017-10-23 17:14:52 -0700102 /**
103 * @see #goToState(LauncherState, boolean, Runnable)
104 */
105 public void goToState(LauncherState state) {
106 goToState(state, true, 0, null);
107 }
108
109 /**
110 * @see #goToState(LauncherState, boolean, Runnable)
111 */
112 public void goToState(LauncherState state, boolean animated) {
113 goToState(state, animated, 0, null);
114 }
115
116 /**
117 * Changes the Launcher state to the provided state.
118 *
119 * @param animated false if the state should change immediately without any animation,
120 * true otherwise
121 * @paras onCompleteRunnable any action to perform at the end of the transition, of null.
122 */
Sunny Goyalbe93f262017-10-20 17:05:27 -0700123 public void goToState(LauncherState state, boolean animated, Runnable onCompleteRunnable) {
Sunny Goyal3e3f44c2017-10-23 17:14:52 -0700124 goToState(state, animated, 0, onCompleteRunnable);
125 }
126
127 /**
128 * Changes the Launcher state to the provided state after the given delay.
129 */
130 public void goToState(LauncherState state, long delay, Runnable onCompleteRunnable) {
131 goToState(state, true, delay, onCompleteRunnable);
132 }
133
134 /**
135 * Changes the Launcher state to the provided state after the given delay.
136 */
137 public void goToState(LauncherState state, long delay) {
138 goToState(state, true, delay, null);
139 }
140
141 private void goToState(LauncherState state, boolean animated, long delay,
142 Runnable onCompleteRunnable) {
Sunny Goyal03102202017-10-27 11:05:26 -0700143 if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
Sunny Goyal3e3f44c2017-10-23 17:14:52 -0700144
145 // Run any queued runnable
146 if (onCompleteRunnable != null) {
147 onCompleteRunnable.run();
148 }
149 return;
150 }
151
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700152 // Cancel the current animation
Sunny Goyalaeb16432017-10-16 11:46:41 -0700153 mConfig.reset();
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700154
Sunny Goyalaeb16432017-10-16 11:46:41 -0700155 if (!animated) {
Sunny Goyalea609262017-10-25 15:47:38 -0700156 setState(state);
Sunny Goyalc4fa8c32017-11-07 12:23:58 -0800157 for (StateHandler handler : getStateHandlers()) {
158 handler.setState(state);
159 }
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700160 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
161
Sunny Goyalbe93f262017-10-20 17:05:27 -0700162 // Run any queued runnable
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700163 if (onCompleteRunnable != null) {
164 onCompleteRunnable.run();
165 }
166 return;
167 }
168
Sunny Goyal03102202017-10-27 11:05:26 -0700169 // Since state NORMAL can be reached from multiple states, just assume that the
170 // transition plays in reverse and use the same duration as previous state.
171 mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
172
173 AnimatorSet animation = createAnimationToNewWorkspaceInternal(state, onCompleteRunnable);
Sunny Goyalbe93f262017-10-20 17:05:27 -0700174 Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
Sunny Goyal3e3f44c2017-10-23 17:14:52 -0700175 if (delay > 0) {
176 mUiHandler.postDelayed(runnable, delay);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700177 } else {
Sunny Goyal03102202017-10-27 11:05:26 -0700178 mUiHandler.post(runnable);
Sunny Goyalf1fbc3f2017-10-10 15:21:15 -0700179 }
Winson Chungb745afb2015-03-02 11:51:23 -0800180 }
181
Sunny Goyal03102202017-10-27 11:05:26 -0700182 /**
183 * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
184 * state transition.
185 * @param state the final state for the transition.
186 * @param duration intended duration for normal playback. Use higher duration for better
187 * accuracy.
188 */
Sunny Goyal85525172017-11-06 13:00:42 -0800189 public AnimatorPlaybackController createAnimationToNewWorkspace(
Sunny Goyal03102202017-10-27 11:05:26 -0700190 LauncherState state, long duration) {
Sunny Goyalaeb16432017-10-16 11:46:41 -0700191 mConfig.reset();
Sunny Goyal03102202017-10-27 11:05:26 -0700192 mConfig.userControlled = true;
193 mConfig.duration = duration;
194 return AnimatorPlaybackController.wrap(
195 createAnimationToNewWorkspaceInternal(state, null), duration);
196 }
197
198 protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
199 final Runnable onCompleteRunnable) {
Sunny Goyalbe93f262017-10-20 17:05:27 -0700200 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
201 final AnimationLayerSet layerViews = new AnimationLayerSet();
202
Sunny Goyalc4fa8c32017-11-07 12:23:58 -0800203 for (StateHandler handler : getStateHandlers()) {
204 handler.setStateWithAnimation(state, layerViews, animation, mConfig);
205 }
Sunny Goyalbe93f262017-10-20 17:05:27 -0700206 animation.addListener(layerViews);
207 animation.addListener(new AnimationSuccessListener() {
Sunny Goyalea609262017-10-25 15:47:38 -0700208
209 @Override
210 public void onAnimationStart(Animator animation) {
211 // Change the internal state only when the transition actually starts
212 setState(state);
213 }
214
Sunny Goyalaeb16432017-10-16 11:46:41 -0700215 @Override
Sunny Goyalbe93f262017-10-20 17:05:27 -0700216 public void onAnimationSuccess(Animator animator) {
Sunny Goyalaeb16432017-10-16 11:46:41 -0700217 // Run any queued runnables
218 if (onCompleteRunnable != null) {
219 onCompleteRunnable.run();
220 }
221
Sunny Goyalbe93f262017-10-20 17:05:27 -0700222 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
Sunny Goyalaeb16432017-10-16 11:46:41 -0700223 }
224 });
Sunny Goyalbe93f262017-10-20 17:05:27 -0700225 mConfig.setAnimation(animation);
226 return mConfig.mCurrentAnimation;
Tony Wickham6d1bbe32015-09-16 12:06:38 -0700227 }
228
Sunny Goyalea609262017-10-25 15:47:38 -0700229 private void setState(LauncherState state) {
230 mState.onStateDisabled(mLauncher);
231 mState = state;
232 mState.onStateEnabled(mLauncher);
233 }
234
Tony Wickham6d1bbe32015-09-16 12:06:38 -0700235 /**
Winson Chungb745afb2015-03-02 11:51:23 -0800236 * Cancels the current animation.
237 */
Sunny Goyalbe93f262017-10-20 17:05:27 -0700238 public void cancelAnimation() {
239 mConfig.reset();
Winson Chung006ee262015-08-03 14:40:11 -0700240 }
Sunny Goyaldb364372016-10-26 19:12:47 -0700241
242 private class StartAnimRunnable implements Runnable {
243
244 private final AnimatorSet mAnim;
245 private final View mViewToFocus;
246
247 public StartAnimRunnable(AnimatorSet anim, View viewToFocus) {
248 mAnim = anim;
249 mViewToFocus = viewToFocus;
250 }
251
252 @Override
253 public void run() {
Sunny Goyalbe93f262017-10-20 17:05:27 -0700254 if (mConfig.mCurrentAnimation != mAnim) {
Sunny Goyaldb364372016-10-26 19:12:47 -0700255 return;
256 }
257 if (mViewToFocus != null) {
258 mViewToFocus.requestFocus();
259 }
260 mAnim.start();
261 }
262 }
Sunny Goyalaeb16432017-10-16 11:46:41 -0700263
Sunny Goyalbe93f262017-10-20 17:05:27 -0700264 public static class AnimationConfig extends AnimatorListenerAdapter {
Sunny Goyalea609262017-10-25 15:47:38 -0700265 public long duration;
Sunny Goyal03102202017-10-27 11:05:26 -0700266 public boolean userControlled;
Sunny Goyalaeb16432017-10-16 11:46:41 -0700267
Sunny Goyalbe93f262017-10-20 17:05:27 -0700268 private AnimatorSet mCurrentAnimation;
Sunny Goyalaeb16432017-10-16 11:46:41 -0700269
270 public void reset() {
Sunny Goyalea609262017-10-25 15:47:38 -0700271 duration = 0;
Sunny Goyal03102202017-10-27 11:05:26 -0700272 userControlled = false;
Sunny Goyalbe93f262017-10-20 17:05:27 -0700273
274 if (mCurrentAnimation != null) {
275 mCurrentAnimation.setDuration(0);
276 mCurrentAnimation.cancel();
277 mCurrentAnimation = null;
278 }
Sunny Goyalaeb16432017-10-16 11:46:41 -0700279 }
280
Sunny Goyalbe93f262017-10-20 17:05:27 -0700281 @Override
282 public void onAnimationEnd(Animator animation) {
283 if (mCurrentAnimation == animation) {
284 mCurrentAnimation = null;
285 }
286 }
287
288 public void setAnimation(AnimatorSet animation) {
289 mCurrentAnimation = animation;
290 mCurrentAnimation.addListener(this);
291 }
Sunny Goyalaeb16432017-10-16 11:46:41 -0700292 }
Sunny Goyalc4fa8c32017-11-07 12:23:58 -0800293
294 public interface StateHandler {
295
296 /**
297 * Updates the UI to {@param state} without any animations
298 */
299 void setState(LauncherState state);
300
301 /**
302 * Sets the UI to {@param state} by animating any changes.
303 */
304 void setStateWithAnimation(LauncherState toState, AnimationLayerSet layerViews,
305 AnimatorSet anim, AnimationConfig config);
306 }
Adam Cohen15588932015-05-26 23:03:31 -0700307}