1 package com.android.launcher3.allapps;
2 
3 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
4 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
5 import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
6 import static com.android.launcher3.LauncherState.OVERVIEW;
7 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
8 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
9 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
10 import static com.android.launcher3.anim.Interpolators.INSTANT;
11 import static com.android.launcher3.anim.Interpolators.LINEAR;
12 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
13 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
14 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
15 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
16 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.ObjectAnimator;
21 import android.content.Context;
22 import android.util.FloatProperty;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.animation.Interpolator;
26 import android.widget.EditText;
27 
28 import com.android.launcher3.DeviceProfile;
29 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
30 import com.android.launcher3.Launcher;
31 import com.android.launcher3.LauncherState;
32 import com.android.launcher3.R;
33 import com.android.launcher3.anim.AnimationSuccessListener;
34 import com.android.launcher3.anim.PendingAnimation;
35 import com.android.launcher3.anim.PropertySetter;
36 import com.android.launcher3.statemanager.StateManager.StateHandler;
37 import com.android.launcher3.states.StateAnimationConfig;
38 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
39 import com.android.launcher3.views.ScrimView;
40 import com.android.systemui.plugins.AllAppsSearchPlugin;
41 import com.android.systemui.plugins.PluginListener;
42 
43 /**
44  * Handles AllApps view transition.
45  * 1) Slides all apps view using direct manipulation
46  * 2) When finger is released, animate to either top or bottom accordingly.
47  * <p/>
48  * Algorithm:
49  * If release velocity > THRES1, snap according to the direction of movement.
50  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
51  * closer to top or closer to the page indicator.
52  */
53 public class AllAppsTransitionController implements StateHandler<LauncherState>,
54         OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> {
55 
56     public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
57             new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
58 
59         @Override
60         public Float get(AllAppsTransitionController controller) {
61             return controller.mProgress;
62         }
63 
64         @Override
65         public void setValue(AllAppsTransitionController controller, float progress) {
66             controller.setProgress(progress);
67         }
68     };
69 
70     private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0;
71 
72     private AllAppsContainerView mAppsView;
73     private ScrimView mScrimView;
74 
75     private final Launcher mLauncher;
76     private boolean mIsVerticalLayout;
77 
78     // Animation in this class is controlled by a single variable {@link mProgress}.
79     // Visually, it represents top y coordinate of the all apps container if multiplied with
80     // {@link mShiftRange}.
81 
82     // When {@link mProgress} is 0, all apps container is pulled up.
83     // When {@link mProgress} is 1, all apps container is pulled down.
84     private float mShiftRange;      // changes depending on the orientation
85     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
86 
87     private float mScrollRangeDelta = 0;
88 
89     // plugin related variables
90     private AllAppsSearchPlugin mPlugin;
91     private View mPluginContent;
92 
AllAppsTransitionController(Launcher l)93     public AllAppsTransitionController(Launcher l) {
94         mLauncher = l;
95         mShiftRange = mLauncher.getDeviceProfile().heightPx;
96         mProgress = 1f;
97 
98         mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
99         mLauncher.addOnDeviceProfileChangeListener(this);
100     }
101 
getShiftRange()102     public float getShiftRange() {
103         return mShiftRange;
104     }
105 
106     @Override
onDeviceProfileChanged(DeviceProfile dp)107     public void onDeviceProfileChanged(DeviceProfile dp) {
108         mIsVerticalLayout = dp.isVerticalBarLayout();
109         setScrollRangeDelta(mScrollRangeDelta);
110 
111         if (mIsVerticalLayout) {
112             mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1);
113             mLauncher.getHotseat().setTranslationY(0);
114             mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
115         }
116     }
117 
118     /**
119      * Note this method should not be called outside this class. This is public because it is used
120      * in xml-based animations which also handle updating the appropriate UI.
121      *
122      * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
123      *
124      * @see #setState(LauncherState)
125      * @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation)
126      */
setProgress(float progress)127     public void setProgress(float progress) {
128         mProgress = progress;
129         mScrimView.setProgress(progress);
130         float shiftCurrent = progress * mShiftRange;
131 
132         mAppsView.setTranslationY(shiftCurrent);
133         if (mPlugin != null) {
134             mPlugin.setProgress(progress);
135         }
136     }
137 
getProgress()138     public float getProgress() {
139         return mProgress;
140     }
141 
142     /**
143      * Sets the vertical transition progress to {@param state} and updates all the dependent UI
144      * accordingly.
145      */
146     @Override
setState(LauncherState state)147     public void setState(LauncherState state) {
148         setProgress(state.getVerticalProgress(mLauncher));
149         setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
150         onProgressAnimationEnd();
151     }
152 
153     /**
154      * Creates an animation which updates the vertical transition progress and updates all the
155      * dependent UI using various animation events
156      */
157     @Override
setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation builder)158     public void setStateWithAnimation(LauncherState toState,
159             StateAnimationConfig config, PendingAnimation builder) {
160         float targetProgress = toState.getVerticalProgress(mLauncher);
161         if (Float.compare(mProgress, targetProgress) == 0) {
162             if (!config.onlyPlayAtomicComponent()) {
163                 setAlphas(toState, config, builder);
164             }
165             // Fail fast
166             onProgressAnimationEnd();
167             return;
168         }
169 
170         if (config.onlyPlayAtomicComponent()) {
171             // There is no atomic component for the all apps transition, so just return early.
172             return;
173         }
174 
175         Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
176                 ? config.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
177                 : FAST_OUT_SLOW_IN;
178 
179         Animator anim = createSpringAnimation(mProgress, targetProgress);
180         anim.setInterpolator(config.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
181         anim.addListener(getProgressAnimatorListener());
182         builder.add(anim);
183 
184         setAlphas(toState, config, builder);
185     }
186 
createSpringAnimation(float... progressValues)187     public Animator createSpringAnimation(float... progressValues) {
188         return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues);
189     }
190 
191     /**
192      * Updates the property for the provided state
193      */
setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter)194     public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) {
195         int visibleElements = state.getVisibleElements(mLauncher);
196         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
197         boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
198 
199         boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
200 
201         Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
202         Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
203 
204         if (mPlugin == null) {
205             setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
206             setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
207             mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
208                     hasAllAppsContent, setter, headerFade, allAppsFade);
209         } else {
210             setter.setViewAlpha(mPluginContent, hasAllAppsContent ? 1 : 0, allAppsFade);
211             setter.setViewAlpha(mAppsView.getContentView(), 0, allAppsFade);
212             setter.setViewAlpha(mAppsView.getScrollBar(), 0, allAppsFade);
213         }
214         mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
215 
216         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
217                 (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
218 
219         // Set visibility of the container at the very beginning or end of the transition.
220         setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
221                 hasAnyVisibleItem ? INSTANT : FINAL_FRAME);
222     }
223 
getProgressAnimatorListener()224     public AnimatorListenerAdapter getProgressAnimatorListener() {
225         return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd);
226     }
227 
setupViews(AllAppsContainerView appsView, ScrimView scrimView)228     public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
229         mAppsView = appsView;
230         mScrimView = scrimView;
231         PluginManagerWrapper.INSTANCE.get(mLauncher)
232                 .addPluginListener(this, AllAppsSearchPlugin.class, false);
233     }
234 
235     /**
236      * Updates the total scroll range but does not update the UI.
237      */
setScrollRangeDelta(float delta)238     void setScrollRangeDelta(float delta) {
239         mScrollRangeDelta = delta;
240         mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta;
241 
242         if (mScrimView != null) {
243             mScrimView.reInitUi();
244         }
245     }
246 
247     /**
248      * Set the final view states based on the progress.
249      * TODO: This logic should go in {@link LauncherState}
250      */
onProgressAnimationEnd()251     private void onProgressAnimationEnd() {
252         if (Float.compare(mProgress, 1f) == 0) {
253             mAppsView.reset(false /* animate */);
254         }
255         updatePluginAnimationEnd();
256     }
257 
258     @Override
onPluginConnected(AllAppsSearchPlugin plugin, Context context)259     public void onPluginConnected(AllAppsSearchPlugin plugin, Context context) {
260         mPlugin = plugin;
261         mPluginContent = mLauncher.getLayoutInflater().inflate(
262                 R.layout.all_apps_content_layout, mAppsView, false);
263         mAppsView.addView(mPluginContent);
264         mPluginContent.setAlpha(0f);
265         mPlugin.setup((ViewGroup) mPluginContent, mLauncher, mShiftRange);
266     }
267 
268     @Override
onPluginDisconnected(AllAppsSearchPlugin plugin)269     public void onPluginDisconnected(AllAppsSearchPlugin plugin) {
270         mPlugin = null;
271         mAppsView.removeView(mPluginContent);
272     }
273 
onActivityDestroyed()274     public void onActivityDestroyed() {
275         PluginManagerWrapper.INSTANCE.get(mLauncher).removePluginListener(this);
276     }
277 
278     /** Used for the plugin to signal when drag starts happens
279      * @param toAllApps*/
onDragStart(boolean toAllApps)280     public void onDragStart(boolean toAllApps) {
281         if (mPlugin == null) return;
282 
283         if (toAllApps) {
284             EditText editText = mAppsView.getSearchUiManager().setTextSearchEnabled(true);
285             mPlugin.setEditText(editText);
286         }
287         mPlugin.onDragStart(toAllApps ? 1f : 0f);
288     }
289 
updatePluginAnimationEnd()290     private void updatePluginAnimationEnd() {
291         if (mPlugin == null) return;
292         mPlugin.onAnimationEnd(mProgress);
293         if (Float.compare(mProgress, 1f) == 0) {
294             mAppsView.getSearchUiManager().setTextSearchEnabled(false);
295             mPlugin.setEditText(null);
296         }
297     }
298 }
299