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 package com.android.launcher3.touch;
17 
18 import static com.android.app.animation.Interpolators.DECELERATED_EASE;
19 import static com.android.app.animation.Interpolators.EMPHASIZED;
20 import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
21 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
22 import static com.android.app.animation.Interpolators.FINAL_FRAME;
23 import static com.android.app.animation.Interpolators.INSTANT;
24 import static com.android.app.animation.Interpolators.LINEAR;
25 import static com.android.app.animation.Interpolators.clampToProgress;
26 import static com.android.app.animation.Interpolators.mapToProgress;
27 import static com.android.launcher3.LauncherState.ALL_APPS;
28 import static com.android.launcher3.LauncherState.NORMAL;
29 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE;
30 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
31 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_KEYBOARD_FADE;
32 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
33 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
34 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
35 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
36 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
37 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
38 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
39 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
40 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
41 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
42 
43 import android.view.MotionEvent;
44 import android.view.animation.Interpolator;
45 
46 import com.android.app.animation.Interpolators;
47 import com.android.launcher3.AbstractFloatingView;
48 import com.android.launcher3.DeviceProfile;
49 import com.android.launcher3.Launcher;
50 import com.android.launcher3.LauncherState;
51 import com.android.launcher3.states.StateAnimationConfig;
52 
53 /**
54  * TouchController to switch between NORMAL and ALL_APPS state.
55  */
56 public class AllAppsSwipeController extends AbstractStateChangeTouchController {
57 
58     private static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
59     private static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
60     private static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = 0.1f;
61     private static final float ALL_APPS_STAGGERED_FADE_THRESHOLD = 0.5f;
62 
63     public static final Interpolator ALL_APPS_SCRIM_RESPONDER =
64             Interpolators.clampToProgress(
65                     LINEAR, ALL_APPS_SCRIM_VISIBLE_THRESHOLD, ALL_APPS_STAGGERED_FADE_THRESHOLD);
66     public static final Interpolator ALL_APPS_CLAMPING_RESPONDER =
67             Interpolators.clampToProgress(
68                     LINEAR,
69                     1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
70                     1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD);
71 
72     // ---- Custom interpolators for NORMAL -> ALL_APPS on phones only. ----
73 
74     public static final float ALL_APPS_STATE_TRANSITION_ATOMIC = 0.3333f;
75     public static final float ALL_APPS_STATE_TRANSITION_MANUAL = 0.4f;
76     private static final float ALL_APPS_FADE_END_ATOMIC = 0.8333f;
77     private static final float ALL_APPS_FADE_END_MANUAL = 0.8f;
78     private static final float ALL_APPS_FULL_DEPTH_PROGRESS = 0.5f;
79     private static final float SCRIM_FADE_START_ATOMIC = 0.2642f;
80     private static final float SCRIM_FADE_START_MANUAL = 0.117f;
81     private static final float WORKSPACE_MOTION_START_ATOMIC = 0.1667f;
82 
83     private static final Interpolator LINEAR_EARLY_MANUAL =
84             Interpolators.clampToProgress(LINEAR, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
85     private static final Interpolator STEP_TRANSITION_ATOMIC =
86             Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION_ATOMIC);
87     private static final Interpolator STEP_TRANSITION_MANUAL =
88             Interpolators.clampToProgress(FINAL_FRAME, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
89 
90     // The blur to All Apps is set to be complete when the interpolator is at 0.5.
91     private static final Interpolator BLUR_ADJUSTED =
92             Interpolators.mapToProgress(LINEAR, 0f, ALL_APPS_FULL_DEPTH_PROGRESS);
93     public static final Interpolator BLUR_ATOMIC =
94             Interpolators.clampToProgress(
95                     BLUR_ADJUSTED, WORKSPACE_MOTION_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
96     public static final Interpolator BLUR_MANUAL =
97             Interpolators.clampToProgress(BLUR_ADJUSTED, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
98 
99     public static final Interpolator WORKSPACE_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
100     public static final Interpolator WORKSPACE_FADE_MANUAL = STEP_TRANSITION_MANUAL;
101 
102     public static final Interpolator WORKSPACE_SCALE_ATOMIC =
103             Interpolators.clampToProgress(
104                     EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
105                     ALL_APPS_STATE_TRANSITION_ATOMIC);
106     public static final Interpolator WORKSPACE_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
107 
108     public static final Interpolator HOTSEAT_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
109     public static final Interpolator HOTSEAT_FADE_MANUAL = STEP_TRANSITION_MANUAL;
110 
111     public static final Interpolator HOTSEAT_SCALE_ATOMIC =
112             Interpolators.clampToProgress(
113                     EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
114                     ALL_APPS_STATE_TRANSITION_ATOMIC);
115     public static final Interpolator HOTSEAT_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
116 
117     public static final Interpolator HOTSEAT_TRANSLATE_ATOMIC = STEP_TRANSITION_ATOMIC;
118     public static final Interpolator HOTSEAT_TRANSLATE_MANUAL = STEP_TRANSITION_MANUAL;
119 
120     public static final Interpolator SCRIM_FADE_ATOMIC =
121             Interpolators.clampToProgress(
122                     Interpolators.mapToProgress(LINEAR, 0f, 0.8f),
123                     SCRIM_FADE_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
124     public static final Interpolator SCRIM_FADE_MANUAL =
125             Interpolators.clampToProgress(
126                     LINEAR, SCRIM_FADE_START_MANUAL, ALL_APPS_STATE_TRANSITION_MANUAL);
127 
128     public static final Interpolator ALL_APPS_FADE_ATOMIC =
129             Interpolators.clampToProgress(
130                     Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.2f, 1f),
131                     ALL_APPS_STATE_TRANSITION_ATOMIC, ALL_APPS_FADE_END_ATOMIC);
132     public static final Interpolator ALL_APPS_FADE_MANUAL =
133             Interpolators.clampToProgress(
134                     LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, ALL_APPS_FADE_END_MANUAL);
135 
136     public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_ATOMIC =
137             Interpolators.clampToProgress(
138                     Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1f),
139                     ALL_APPS_STATE_TRANSITION_ATOMIC, 1f);
140     public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL = LINEAR;
141 
142     // --------
143 
AllAppsSwipeController(Launcher l)144     public AllAppsSwipeController(Launcher l) {
145         super(l, SingleAxisSwipeDetector.VERTICAL);
146     }
147 
148     @Override
canInterceptTouch(MotionEvent ev)149     protected boolean canInterceptTouch(MotionEvent ev) {
150         if (mCurrentAnimation != null) {
151             // If we are already animating from a previous state, we can intercept.
152             return true;
153         }
154         if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
155             return false;
156         }
157         if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
158             // Don't listen for the swipe gesture if we are already in some other state.
159             return false;
160         }
161         if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
162             return false;
163         }
164         return true;
165     }
166 
167     @Override
getTargetState(LauncherState fromState, boolean isDragTowardPositive)168     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
169         if (fromState == NORMAL && shouldOpenAllApps(isDragTowardPositive)) {
170             return ALL_APPS;
171         } else if (fromState == ALL_APPS && !isDragTowardPositive) {
172             return NORMAL;
173         }
174         return fromState;
175     }
176 
177     @Override
initCurrentAnimation()178     protected float initCurrentAnimation() {
179         float range = getShiftRange();
180         StateAnimationConfig config = getConfigForStates(mFromState, mToState);
181         config.duration = (long) (2 * range);
182 
183         mCurrentAnimation = mLauncher.getStateManager()
184                 .createAnimationToNewWorkspace(mToState, config);
185         float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
186         float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
187         float totalShift = endVerticalShift - startVerticalShift;
188         return 1 / totalShift;
189     }
190 
191     @Override
getConfigForStates(LauncherState fromState, LauncherState toState)192     protected StateAnimationConfig getConfigForStates(LauncherState fromState,
193             LauncherState toState) {
194         StateAnimationConfig config = super.getConfigForStates(fromState, toState);
195         config.animProps |= StateAnimationConfig.USER_CONTROLLED;
196         if (fromState == NORMAL && toState == ALL_APPS) {
197             applyNormalToAllAppsAnimConfig(mLauncher, config);
198         } else if (fromState == ALL_APPS && toState == NORMAL) {
199             applyAllAppsToNormalConfig(mLauncher, config);
200         }
201         return config;
202     }
203 
204     /**
205      * Applies Animation config values for transition from all apps to home.
206      */
applyAllAppsToNormalConfig(Launcher launcher, StateAnimationConfig config)207     public static void applyAllAppsToNormalConfig(Launcher launcher, StateAnimationConfig config) {
208         if (launcher.getDeviceProfile().isTablet) {
209             config.setInterpolator(ANIM_SCRIM_FADE,
210                     Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
211             config.setInterpolator(ANIM_ALL_APPS_FADE, FINAL_FRAME);
212             if (!config.isUserControlled()) {
213                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
214             }
215             config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
216             config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
217         } else {
218             if (config.isUserControlled()) {
219                 config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
220                 config.setInterpolator(ANIM_WORKSPACE_FADE,
221                         Interpolators.reverse(WORKSPACE_FADE_MANUAL));
222                 config.setInterpolator(ANIM_WORKSPACE_SCALE,
223                         Interpolators.reverse(WORKSPACE_SCALE_MANUAL));
224                 config.setInterpolator(ANIM_HOTSEAT_FADE,
225                         Interpolators.reverse(HOTSEAT_FADE_MANUAL));
226                 config.setInterpolator(ANIM_HOTSEAT_SCALE,
227                         Interpolators.reverse(HOTSEAT_SCALE_MANUAL));
228                 config.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
229                         Interpolators.reverse(HOTSEAT_TRANSLATE_MANUAL));
230                 config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.reverse(SCRIM_FADE_MANUAL));
231                 config.setInterpolator(ANIM_ALL_APPS_FADE,
232                         Interpolators.reverse(ALL_APPS_FADE_MANUAL));
233                 config.setInterpolator(ANIM_VERTICAL_PROGRESS,
234                         Interpolators.reverse(ALL_APPS_VERTICAL_PROGRESS_MANUAL));
235             } else {
236                 config.setInterpolator(ANIM_SCRIM_FADE,
237                         Interpolators.reverse(ALL_APPS_SCRIM_RESPONDER));
238                 config.setInterpolator(ANIM_ALL_APPS_FADE, ALL_APPS_CLAMPING_RESPONDER);
239                 config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
240                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED_ACCELERATE);
241             }
242         }
243     }
244 
245     /**
246      * Applies Animation config values for transition from home to all apps.
247      */
applyNormalToAllAppsAnimConfig( Launcher launcher, StateAnimationConfig config)248     public static void applyNormalToAllAppsAnimConfig(
249             Launcher launcher, StateAnimationConfig config) {
250         if (launcher.getDeviceProfile().isTablet) {
251             config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
252             config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
253             if (!config.isUserControlled()) {
254                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, EMPHASIZED);
255             }
256             config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
257             config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
258         } else {
259             config.setInterpolator(ANIM_DEPTH,
260                     config.isUserControlled() ? BLUR_MANUAL : BLUR_ATOMIC);
261             config.setInterpolator(ANIM_WORKSPACE_FADE,
262                     config.isUserControlled() ? WORKSPACE_FADE_MANUAL : WORKSPACE_FADE_ATOMIC);
263             config.setInterpolator(ANIM_WORKSPACE_SCALE,
264                     config.isUserControlled() ? WORKSPACE_SCALE_MANUAL : WORKSPACE_SCALE_ATOMIC);
265             config.setInterpolator(ANIM_HOTSEAT_FADE,
266                     config.isUserControlled() ? HOTSEAT_FADE_MANUAL : HOTSEAT_FADE_ATOMIC);
267             config.setInterpolator(ANIM_HOTSEAT_SCALE,
268                     config.isUserControlled() ? HOTSEAT_SCALE_MANUAL : HOTSEAT_SCALE_ATOMIC);
269             config.setInterpolator(ANIM_HOTSEAT_TRANSLATE,
270                     config.isUserControlled()
271                             ? HOTSEAT_TRANSLATE_MANUAL
272                             : HOTSEAT_TRANSLATE_ATOMIC);
273             config.setInterpolator(ANIM_SCRIM_FADE,
274                     config.isUserControlled() ? SCRIM_FADE_MANUAL : SCRIM_FADE_ATOMIC);
275             config.setInterpolator(ANIM_ALL_APPS_FADE,
276                     config.isUserControlled() ? ALL_APPS_FADE_MANUAL : ALL_APPS_FADE_ATOMIC);
277             config.setInterpolator(ANIM_VERTICAL_PROGRESS,
278                     config.isUserControlled()
279                             ? ALL_APPS_VERTICAL_PROGRESS_MANUAL
280                             : ALL_APPS_VERTICAL_PROGRESS_ATOMIC);
281         }
282     }
283 
284     /**
285      * Applies Animation config values for transition from overview to all apps.
286      *
287      * @param threshold progress at which all apps will open upon release
288      */
applyOverviewToAllAppsAnimConfig( DeviceProfile deviceProfile, StateAnimationConfig config, float threshold)289     public static void applyOverviewToAllAppsAnimConfig(
290             DeviceProfile deviceProfile, StateAnimationConfig config, float threshold) {
291         config.animProps |= StateAnimationConfig.USER_CONTROLLED;
292         config.animFlags = SKIP_OVERVIEW;
293         if (deviceProfile.isTablet) {
294             config.setInterpolator(ANIM_ALL_APPS_FADE, INSTANT);
295             config.setInterpolator(ANIM_SCRIM_FADE, ALL_APPS_SCRIM_RESPONDER);
296             // The fact that we end on Workspace is not very ideal, but since we do, fade it in at
297             // the end of the transition. Don't scale/translate it.
298             config.setInterpolator(ANIM_WORKSPACE_FADE, clampToProgress(LINEAR, 0.8f, 1));
299             config.setInterpolator(ANIM_WORKSPACE_SCALE, INSTANT);
300             config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, INSTANT);
301         } else {
302             // Pop the background panel, keyboard, and content in at full opacity at the threshold.
303             config.setInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE,
304                     thresholdInterpolator(threshold, INSTANT));
305             config.setInterpolator(ANIM_ALL_APPS_KEYBOARD_FADE,
306                     thresholdInterpolator(threshold, INSTANT));
307             config.setInterpolator(ANIM_ALL_APPS_FADE, thresholdInterpolator(threshold, INSTANT));
308 
309             config.setInterpolator(ANIM_VERTICAL_PROGRESS,
310                     thresholdInterpolator(threshold, mapToProgress(LINEAR, threshold, 1f)));
311         }
312     }
313 
314     /** Creates an interpolator that is 0 until the threshold, then follows given interpolator. */
thresholdInterpolator(float threshold, Interpolator interpolator)315     private static Interpolator thresholdInterpolator(float threshold, Interpolator interpolator) {
316         return progress -> progress <= threshold ? 0 : interpolator.getInterpolation(progress);
317     }
318 }
319