Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 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 | |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 19 | import static com.android.launcher3.LauncherState.NORMAL; |
| 20 | |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 21 | import android.animation.Animator; |
| 22 | import android.animation.AnimatorListenerAdapter; |
| 23 | import android.animation.AnimatorSet; |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 24 | import android.os.Handler; |
| 25 | import android.os.Looper; |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 26 | import android.view.View; |
Adam Cohen | 1558893 | 2015-05-26 23:03:31 -0700 | [diff] [blame] | 27 | |
Hyunyoung Song | 645764e | 2016-06-06 14:19:02 -0700 | [diff] [blame] | 28 | import com.android.launcher3.allapps.AllAppsTransitionController; |
Sunny Goyal | b5e65c8 | 2016-10-26 18:32:38 -0700 | [diff] [blame] | 29 | import com.android.launcher3.anim.AnimationLayerSet; |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 30 | import com.android.launcher3.anim.AnimationSuccessListener; |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 31 | import com.android.launcher3.anim.AnimatorPlaybackController; |
Sunny Goyal | c4fa8c3 | 2017-11-07 12:23:58 -0800 | [diff] [blame^] | 32 | import com.android.launcher3.uioverrides.UiFactory; |
Adam Cohen | 1558893 | 2015-05-26 23:03:31 -0700 | [diff] [blame] | 33 | |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 34 | /** |
| 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 Goyal | 3e3f44c | 2017-10-23 17:14:52 -0700 | [diff] [blame] | 75 | public class LauncherStateManager { |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 76 | |
Sunny Goyal | 3e3f44c | 2017-10-23 17:14:52 -0700 | [diff] [blame] | 77 | public static final String TAG = "StateManager"; |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 78 | |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 79 | private final AnimationConfig mConfig = new AnimationConfig(); |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 80 | private final Handler mUiHandler; |
| 81 | private final Launcher mLauncher; |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 82 | |
Sunny Goyal | c4fa8c3 | 2017-11-07 12:23:58 -0800 | [diff] [blame^] | 83 | private StateHandler[] mStateHandlers; |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 84 | private LauncherState mState = NORMAL; |
| 85 | |
Sunny Goyal | c4fa8c3 | 2017-11-07 12:23:58 -0800 | [diff] [blame^] | 86 | public LauncherStateManager(Launcher l) { |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 87 | mUiHandler = new Handler(Looper.getMainLooper()); |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 88 | mLauncher = l; |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 89 | } |
| 90 | |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 91 | public LauncherState getState() { |
| 92 | return mState; |
| 93 | } |
| 94 | |
Sunny Goyal | c4fa8c3 | 2017-11-07 12:23:58 -0800 | [diff] [blame^] | 95 | private StateHandler[] getStateHandlers() { |
| 96 | if (mStateHandlers == null) { |
| 97 | mStateHandlers = UiFactory.getStateHandler(mLauncher); |
| 98 | } |
| 99 | return mStateHandlers; |
| 100 | } |
| 101 | |
Sunny Goyal | 3e3f44c | 2017-10-23 17:14:52 -0700 | [diff] [blame] | 102 | /** |
| 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 Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 123 | public void goToState(LauncherState state, boolean animated, Runnable onCompleteRunnable) { |
Sunny Goyal | 3e3f44c | 2017-10-23 17:14:52 -0700 | [diff] [blame] | 124 | 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 Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 143 | if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) { |
Sunny Goyal | 3e3f44c | 2017-10-23 17:14:52 -0700 | [diff] [blame] | 144 | |
| 145 | // Run any queued runnable |
| 146 | if (onCompleteRunnable != null) { |
| 147 | onCompleteRunnable.run(); |
| 148 | } |
| 149 | return; |
| 150 | } |
| 151 | |
Sunny Goyal | f1fbc3f | 2017-10-10 15:21:15 -0700 | [diff] [blame] | 152 | // Cancel the current animation |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 153 | mConfig.reset(); |
Sunny Goyal | f1fbc3f | 2017-10-10 15:21:15 -0700 | [diff] [blame] | 154 | |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 155 | if (!animated) { |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 156 | setState(state); |
Sunny Goyal | c4fa8c3 | 2017-11-07 12:23:58 -0800 | [diff] [blame^] | 157 | for (StateHandler handler : getStateHandlers()) { |
| 158 | handler.setState(state); |
| 159 | } |
Sunny Goyal | f1fbc3f | 2017-10-10 15:21:15 -0700 | [diff] [blame] | 160 | mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); |
| 161 | |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 162 | // Run any queued runnable |
Sunny Goyal | f1fbc3f | 2017-10-10 15:21:15 -0700 | [diff] [blame] | 163 | if (onCompleteRunnable != null) { |
| 164 | onCompleteRunnable.run(); |
| 165 | } |
| 166 | return; |
| 167 | } |
| 168 | |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 169 | // 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 Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 174 | Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher)); |
Sunny Goyal | 3e3f44c | 2017-10-23 17:14:52 -0700 | [diff] [blame] | 175 | if (delay > 0) { |
| 176 | mUiHandler.postDelayed(runnable, delay); |
Sunny Goyal | f1fbc3f | 2017-10-10 15:21:15 -0700 | [diff] [blame] | 177 | } else { |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 178 | mUiHandler.post(runnable); |
Sunny Goyal | f1fbc3f | 2017-10-10 15:21:15 -0700 | [diff] [blame] | 179 | } |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 180 | } |
| 181 | |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 182 | /** |
| 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 Goyal | 8552517 | 2017-11-06 13:00:42 -0800 | [diff] [blame] | 189 | public AnimatorPlaybackController createAnimationToNewWorkspace( |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 190 | LauncherState state, long duration) { |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 191 | mConfig.reset(); |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 192 | 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 Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 200 | final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); |
| 201 | final AnimationLayerSet layerViews = new AnimationLayerSet(); |
| 202 | |
Sunny Goyal | c4fa8c3 | 2017-11-07 12:23:58 -0800 | [diff] [blame^] | 203 | for (StateHandler handler : getStateHandlers()) { |
| 204 | handler.setStateWithAnimation(state, layerViews, animation, mConfig); |
| 205 | } |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 206 | animation.addListener(layerViews); |
| 207 | animation.addListener(new AnimationSuccessListener() { |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 208 | |
| 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 Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 215 | @Override |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 216 | public void onAnimationSuccess(Animator animator) { |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 217 | // Run any queued runnables |
| 218 | if (onCompleteRunnable != null) { |
| 219 | onCompleteRunnable.run(); |
| 220 | } |
| 221 | |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 222 | mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 223 | } |
| 224 | }); |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 225 | mConfig.setAnimation(animation); |
| 226 | return mConfig.mCurrentAnimation; |
Tony Wickham | 6d1bbe3 | 2015-09-16 12:06:38 -0700 | [diff] [blame] | 227 | } |
| 228 | |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 229 | private void setState(LauncherState state) { |
| 230 | mState.onStateDisabled(mLauncher); |
| 231 | mState = state; |
| 232 | mState.onStateEnabled(mLauncher); |
| 233 | } |
| 234 | |
Tony Wickham | 6d1bbe3 | 2015-09-16 12:06:38 -0700 | [diff] [blame] | 235 | /** |
Winson Chung | b745afb | 2015-03-02 11:51:23 -0800 | [diff] [blame] | 236 | * Cancels the current animation. |
| 237 | */ |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 238 | public void cancelAnimation() { |
| 239 | mConfig.reset(); |
Winson Chung | 006ee26 | 2015-08-03 14:40:11 -0700 | [diff] [blame] | 240 | } |
Sunny Goyal | db36437 | 2016-10-26 19:12:47 -0700 | [diff] [blame] | 241 | |
| 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 Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 254 | if (mConfig.mCurrentAnimation != mAnim) { |
Sunny Goyal | db36437 | 2016-10-26 19:12:47 -0700 | [diff] [blame] | 255 | return; |
| 256 | } |
| 257 | if (mViewToFocus != null) { |
| 258 | mViewToFocus.requestFocus(); |
| 259 | } |
| 260 | mAnim.start(); |
| 261 | } |
| 262 | } |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 263 | |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 264 | public static class AnimationConfig extends AnimatorListenerAdapter { |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 265 | public long duration; |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 266 | public boolean userControlled; |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 267 | |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 268 | private AnimatorSet mCurrentAnimation; |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 269 | |
| 270 | public void reset() { |
Sunny Goyal | ea60926 | 2017-10-25 15:47:38 -0700 | [diff] [blame] | 271 | duration = 0; |
Sunny Goyal | 0310220 | 2017-10-27 11:05:26 -0700 | [diff] [blame] | 272 | userControlled = false; |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 273 | |
| 274 | if (mCurrentAnimation != null) { |
| 275 | mCurrentAnimation.setDuration(0); |
| 276 | mCurrentAnimation.cancel(); |
| 277 | mCurrentAnimation = null; |
| 278 | } |
Sunny Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 279 | } |
| 280 | |
Sunny Goyal | be93f26 | 2017-10-20 17:05:27 -0700 | [diff] [blame] | 281 | @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 Goyal | aeb1643 | 2017-10-16 11:46:41 -0700 | [diff] [blame] | 292 | } |
Sunny Goyal | c4fa8c3 | 2017-11-07 12:23:58 -0800 | [diff] [blame^] | 293 | |
| 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 Cohen | 1558893 | 2015-05-26 23:03:31 -0700 | [diff] [blame] | 307 | } |