1 /*
2  * Copyright (C) 2017 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;
17 
18 import static com.android.app.animation.Interpolators.ACCELERATE_2;
19 import static com.android.app.animation.Interpolators.DECELERATE_2;
20 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
21 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
22 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
23 import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
24 import static com.android.launcher3.testing.shared.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
25 import static com.android.launcher3.testing.shared.TestProtocol.EDIT_MODE_STATE_ORDINAL;
26 import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL;
27 import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
28 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
29 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
30 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
31 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
32 import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
33 import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
34 
35 import android.content.Context;
36 import android.graphics.Color;
37 import android.view.View;
38 import android.view.animation.Interpolator;
39 
40 import androidx.annotation.FloatRange;
41 import androidx.annotation.StringRes;
42 
43 import com.android.launcher3.statemanager.BaseState;
44 import com.android.launcher3.statemanager.StateManager;
45 import com.android.launcher3.states.EditModeState;
46 import com.android.launcher3.states.HintState;
47 import com.android.launcher3.states.SpringLoadedState;
48 import com.android.launcher3.testing.shared.TestProtocol;
49 import com.android.launcher3.uioverrides.states.AllAppsState;
50 import com.android.launcher3.uioverrides.states.OverviewState;
51 import com.android.launcher3.views.ActivityContext;
52 
53 import java.util.Arrays;
54 
55 /**
56  * Base state for various states used for the Launcher
57  */
58 public abstract class LauncherState implements BaseState<LauncherState> {
59 
60     /**
61      * Set of elements indicating various workspace elements which change visibility across states
62      * Note that workspace is not included here as in that case, we animate individual pages
63      */
64     public static final int NONE = 0;
65     public static final int HOTSEAT_ICONS = 1 << 0;
66     public static final int ALL_APPS_CONTENT = 1 << 1;
67     public static final int VERTICAL_SWIPE_INDICATOR = 1 << 2;
68     public static final int OVERVIEW_ACTIONS = 1 << 3;
69     public static final int CLEAR_ALL_BUTTON = 1 << 4;
70     public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
71     public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
72     public static final int FLOATING_SEARCH_BAR = 1 << 7;
73 
74     // Flag indicating workspace has multiple pages visible.
75     public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
76     // Flag indicating that workspace and its contents are not accessible
77     public static final int FLAG_WORKSPACE_INACCESSIBLE = BaseState.getFlag(1);
78 
79     // Flag indicating the state allows workspace icons to be dragged.
80     public static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = BaseState.getFlag(2);
81     // Flag to indicate that workspace should draw page background
82     public static final int FLAG_WORKSPACE_HAS_BACKGROUNDS = BaseState.getFlag(3);
83     // Flag to indicate if the state would have scrim over sysui region: statu sbar and nav bar
84     public static final int FLAG_HAS_SYS_UI_SCRIM = BaseState.getFlag(4);
85     // Flag to inticate that all popups should be closed when this state is enabled.
86     public static final int FLAG_CLOSE_POPUPS = BaseState.getFlag(5);
87     public static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(6);
88 
89     // Flag indicating that hotseat and its contents are not accessible.
90     public static final int FLAG_HOTSEAT_INACCESSIBLE = BaseState.getFlag(7);
91 
92 
93     public static final float NO_OFFSET = 0;
94     public static final float NO_SCALE = 1;
95 
96     protected static final PageAlphaProvider DEFAULT_ALPHA_PROVIDER =
97             new PageAlphaProvider(ACCELERATE_2) {
98                 @Override
99                 public float getPageAlpha(int pageIndex) {
100                     return 1;
101                 }
102             };
103 
104     protected static final PageTranslationProvider DEFAULT_PAGE_TRANSLATION_PROVIDER =
105             new PageTranslationProvider(DECELERATE_2) {
106                 @Override
107                 public float getPageTranslation(int pageIndex) {
108                     return 0;
109                 }
110             };
111 
112     private static final LauncherState[] sAllStates = new LauncherState[11];
113 
114     /**
115      * TODO: Create a separate class for NORMAL state.
116      */
117     public static final LauncherState NORMAL = new LauncherState(NORMAL_STATE_ORDINAL,
118             LAUNCHER_STATE_HOME,
119             FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HAS_SYS_UI_SCRIM) {
120         @Override
121         public int getTransitionDuration(Context context, boolean isToState) {
122             // Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
123             return 0;
124         }
125     };
126 
127     /**
128      * Various Launcher states arranged in the increasing order of UI layers
129      */
130     public static final LauncherState SPRING_LOADED = new SpringLoadedState(
131             SPRING_LOADED_STATE_ORDINAL);
132     public static final LauncherState EDIT_MODE = new EditModeState(EDIT_MODE_STATE_ORDINAL);
133     public static final LauncherState ALL_APPS = new AllAppsState(ALL_APPS_STATE_ORDINAL);
134     public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL);
135     public static final LauncherState HINT_STATE_TWO_BUTTON = new HintState(
136             HINT_STATE_TWO_BUTTON_ORDINAL, LAUNCHER_STATE_OVERVIEW);
137 
138     public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
139     public static final LauncherState OVERVIEW_MODAL_TASK = OverviewState.newModalTaskState(
140             OVERVIEW_MODAL_TASK_STATE_ORDINAL);
141     /**
142      * State when user performs a quickswitch gesture from home/workspace to the most recent
143      * app
144      */
145     public static final LauncherState QUICK_SWITCH_FROM_HOME =
146             OverviewState.newSwitchState(QUICK_SWITCH_STATE_ORDINAL);
147     public static final LauncherState BACKGROUND_APP =
148             OverviewState.newBackgroundState(BACKGROUND_APP_STATE_ORDINAL);
149     public static final LauncherState OVERVIEW_SPLIT_SELECT =
150             OverviewState.newSplitSelectState(OVERVIEW_SPLIT_SELECT_ORDINAL);
151 
152     public final int ordinal;
153 
154     /**
155      * Used for {@link com.android.launcher3.logging.StatsLogManager}
156      */
157     public final int statsLogOrdinal;
158 
159     /**
160      * True if the state has overview panel visible.
161      */
162     public final boolean isRecentsViewVisible;
163 
164     private final int mFlags;
165 
LauncherState(int id, int statsLogOrdinal, int flags)166     public LauncherState(int id, int statsLogOrdinal, int flags) {
167         this.statsLogOrdinal = statsLogOrdinal;
168         this.mFlags = flags;
169         this.isRecentsViewVisible = (flags & FLAG_RECENTS_VIEW_VISIBLE) != 0;
170         this.ordinal = id;
171         sAllStates[id] = this;
172     }
173 
174     /**
175      * Returns if the state has the provided flag
176      */
177     @Override
hasFlag(int mask)178     public final boolean hasFlag(int mask) {
179         return (mFlags & mask) != 0;
180     }
181 
values()182     public static LauncherState[] values() {
183         return Arrays.copyOf(sAllStates, sAllStates.length);
184     }
185 
getWorkspaceScaleAndTranslation(Launcher launcher)186     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
187         return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
188     }
189 
getHotseatScaleAndTranslation(Launcher launcher)190     public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
191         // For most states, treat the hotseat as if it were part of the workspace.
192         return getWorkspaceScaleAndTranslation(launcher);
193     }
194 
195     /**
196      * Returns an array of two elements.
197      * The first specifies the scale for the overview
198      * The second is the factor ([0, 1], 0 => center-screen; 1 => offscreen) by which overview
199      * should be shifted horizontally.
200      */
getOverviewScaleAndOffset(Launcher launcher)201     public float[] getOverviewScaleAndOffset(Launcher launcher) {
202         return launcher.getNormalOverviewScaleAndOffset();
203     }
204 
getOverviewFullscreenProgress()205     public float getOverviewFullscreenProgress() {
206         return 0;
207     }
208 
209     /**
210      * How far from the bottom of the screen the <em>floating</em> search bar should rest in this
211      * state when the IME is not present.
212      * <p>
213      * To hide offscreen, use a negative value.
214      * <p>
215      * Note: if the provided value is non-negative but less than the current bottom insets, the
216      * insets will be applied. As such, you can use 0 to default to this.
217      */
getFloatingSearchBarRestingMarginBottom(Launcher launcher)218     public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
219         DeviceProfile dp = launcher.getDeviceProfile();
220         return areElementsVisible(launcher, FLOATING_SEARCH_BAR) ? dp.getQsbOffsetY()
221                 : -dp.hotseatQsbHeight;
222     }
223 
224     /**
225      * How far from the start of the screen the <em>floating</em> search bar should rest.
226      * <p>
227      * To use original margin, return a negative value.
228      */
getFloatingSearchBarRestingMarginStart(Launcher launcher)229     public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
230         boolean isRtl = Utilities.isRtl(launcher.getResources());
231         View qsb = launcher.getHotseat().getQsb();
232         return isRtl ? launcher.getHotseat().getRight() - qsb.getRight() : qsb.getLeft();
233     }
234 
235     /**
236      * How far from the end of the screen the <em>floating</em> search bar should rest.
237      * <p>
238      * To use original margin, return a negative value.
239      */
getFloatingSearchBarRestingMarginEnd(Launcher launcher)240     public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
241         DeviceProfile dp = launcher.getDeviceProfile();
242         if (dp.isQsbInline) {
243             int marginStart = getFloatingSearchBarRestingMarginStart(launcher);
244             return dp.widthPx - marginStart - dp.hotseatQsbWidth;
245         }
246 
247         boolean isRtl = Utilities.isRtl(launcher.getResources());
248         View qsb = launcher.getHotseat().getQsb();
249         return isRtl ? qsb.getLeft() : launcher.getHotseat().getRight() - qsb.getRight();
250     }
251 
252     /** Whether the <em>floating</em> search bar should use the pill UI when not focused. */
shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher)253     public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
254         return false;
255     }
256 
getVisibleElements(Launcher launcher)257     public int getVisibleElements(Launcher launcher) {
258         int elements = HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR;
259         // Floating search bar is visible in normal state except in landscape on phones.
260         if (!(launcher.getDeviceProfile().isPhone && launcher.getDeviceProfile().isLandscape)) {
261             elements |= FLOATING_SEARCH_BAR;
262         }
263         return elements;
264     }
265 
266     /**
267      * A shorthand for checking getVisibleElements() & elements == elements.
268      * @return Whether all of the given elements are visible.
269      */
areElementsVisible(Launcher launcher, int elements)270     public boolean areElementsVisible(Launcher launcher, int elements) {
271         return (getVisibleElements(launcher) & elements) == elements;
272     }
273 
274     /**
275      * Returns whether taskbar is stashed and thus should either:
276      * 1) replace hotseat or taskbar icons with a handle in gesture navigation mode or
277      * 2) fade out the hotseat or taskbar icons in 3-button navigation mode.
278      */
isTaskbarStashed(Launcher launcher)279     public boolean isTaskbarStashed(Launcher launcher) {
280         return false;
281     }
282 
283     /** Returns whether taskbar is aligned with the hotseat vs position inside apps */
isTaskbarAlignedWithHotseat(Launcher launcher)284     public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
285         return true;
286     }
287 
288     /**
289      * Returns whether taskbar global drag is disallowed in this state.
290      */
disallowTaskbarGlobalDrag()291     public boolean disallowTaskbarGlobalDrag() {
292         return false;
293     }
294 
295     /**
296      * Returns whether the taskbar shortcut should trigger split selection mode.
297      */
allowTaskbarInitialSplitSelection()298     public boolean allowTaskbarInitialSplitSelection() {
299         return false;
300     }
301 
302     /**
303      * Fraction shift in the vertical translation UI and related properties
304      *
305      * @see com.android.launcher3.allapps.AllAppsTransitionController
306      */
getVerticalProgress(Launcher launcher)307     public float getVerticalProgress(Launcher launcher) {
308         return 1f;
309     }
310 
getWorkspaceBackgroundAlpha(Launcher launcher)311     public float getWorkspaceBackgroundAlpha(Launcher launcher) {
312         return 0;
313     }
314 
315     /**
316      * What color should the workspace scrim be in when at rest in this state.
317      * Return {@link Color#TRANSPARENT} for no scrim.
318      */
getWorkspaceScrimColor(Launcher launcher)319     public int getWorkspaceScrimColor(Launcher launcher) {
320         return Color.TRANSPARENT;
321     }
322 
323     /**
324      * For this state, how modal should over view been shown. 0 modalness means all tasks drawn,
325      * 1 modalness means the current task is show on its own.
326      */
getOverviewModalness()327     public float getOverviewModalness() {
328         return 0;
329     }
330 
331     /**
332      * For this state, how much additional translation there should be for each of the
333      * child TaskViews. Note that the translation can be its primary or secondary dimension.
334      */
getSplitSelectTranslation(Launcher launcher)335     public float getSplitSelectTranslation(Launcher launcher) {
336         return 0;
337     }
338 
339     /**
340      * The amount of blur and wallpaper zoom to apply to the background of either the app
341      * or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
342      *
343      * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
344      */
345     public final  <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
getDepth(DEVICE_PROFILE_CONTEXT context)346             float getDepth(DEVICE_PROFILE_CONTEXT context) {
347         return getDepth(context,
348                 BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode);
349     }
350 
351     /**
352      * Returns the amount of blur and wallpaper zoom for this state with {@param isMultiWindowMode}.
353      *
354      * @see #getDepth(Context).
355      */
356     public final <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
getDepth(DEVICE_PROFILE_CONTEXT context, boolean isMultiWindowMode)357             float getDepth(DEVICE_PROFILE_CONTEXT context, boolean isMultiWindowMode) {
358         if (isMultiWindowMode) {
359             return 0;
360         }
361         return getDepthUnchecked(context);
362     }
363 
364     protected <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
getDepthUnchecked(DEVICE_PROFILE_CONTEXT context)365             float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) {
366         return 0f;
367     }
368 
getDescription(Launcher launcher)369     public String getDescription(Launcher launcher) {
370         return launcher.getWorkspace().getCurrentPageDescription();
371     }
372 
getTitle()373     public @StringRes int getTitle() {
374         return R.string.home_screen;
375     }
376 
getWorkspacePageAlphaProvider(Launcher launcher)377     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
378         if ((this != NORMAL && this != HINT_STATE)
379                 || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
380             return DEFAULT_ALPHA_PROVIDER;
381         }
382         final int centerPage = launcher.getWorkspace().getNextPage();
383         return new PageAlphaProvider(ACCELERATE_2) {
384             @Override
385             public float getPageAlpha(int pageIndex) {
386                 return pageIndex != centerPage ? 0 : 1f;
387             }
388         };
389     }
390 
391     /**
392      * Gets the translation provider for workspace pages.
393      */
394     public PageTranslationProvider getWorkspacePageTranslationProvider(Launcher launcher) {
395         if (!(this == SPRING_LOADED || this == EDIT_MODE)
396                 || !launcher.getDeviceProfile().isTwoPanels) {
397             return DEFAULT_PAGE_TRANSLATION_PROVIDER;
398         }
399         final float quarterPageSpacing = launcher.getWorkspace().getPageSpacing() / 4f;
400         return new PageTranslationProvider(DECELERATE_2) {
401             @Override
402             public float getPageTranslation(int pageIndex) {
403                 boolean isRtl = launcher.getWorkspace().mIsRtl;
404                 boolean isFirstPage = pageIndex % 2 == 0;
405                 return ((isFirstPage && !isRtl) || (!isFirstPage && isRtl)) ? -quarterPageSpacing
406                         : quarterPageSpacing;
407             }
408         };
409     }
410 
411     /**
412      * Called when leaving this LauncherState
413      * @param launcher - Launcher instance
414      * @param toState - New LauncherState that is being entered
415      */
416     public void onLeavingState(Launcher launcher, LauncherState toState) {
417         // no-op
418         // override to handle when leaving current LauncherState
419     }
420 
421     @Override
422     public LauncherState getHistoryForState(LauncherState previousState) {
423         // No history is supported
424         return NORMAL;
425     }
426 
427     @Override
428     public String toString() {
429         return TestProtocol.stateOrdinalToString(ordinal);
430     }
431 
432     /** Called when predictive back gesture is started. */
433     public void onBackStarted(Launcher launcher) {}
434 
435     /**
436      * Called when back action is invoked. This can happen when:
437      * 1. back button is pressed in 3-button navigation.
438      * 2. when back is committed during back swiped (predictive or non-predictive).
439      * 3. when we programmatically perform back action.
440      */
441     public void onBackInvoked(Launcher launcher) {
442         if (this != NORMAL) {
443             StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
444             LauncherState lastState = lsm.getLastState();
445             lsm.goToState(lastState, forEndCallback(this::onBackAnimationCompleted));
446         }
447     }
448 
449     /**
450      * To be called if back animation is completed in a launcher state.
451      *
452      * @param success whether back animation was successful or canceled.
453      */
454     protected void onBackAnimationCompleted(boolean success) {
455         // Do nothing. To be overridden by child class.
456     }
457 
458     /**
459      * Find {@link StateManager} and target {@link LauncherState} to handle back progress in
460      * predictive back gesture.
461      */
462     public void onBackProgressed(
463             Launcher launcher, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
464         StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
465         LauncherState toState = lsm.getLastState();
466         lsm.onBackProgressed(toState, backProgress);
467     }
468 
469     /**
470      * Find {@link StateManager} and target {@link LauncherState} to handle backProgress in
471      * predictive back gesture.
472      */
473     public void onBackCancelled(Launcher launcher) {
474         StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
475         LauncherState toState = lsm.getLastState();
476         lsm.onBackCancelled(toState);
477     }
478 
479     public static abstract class PageAlphaProvider {
480 
481         public final Interpolator interpolator;
482 
483         public PageAlphaProvider(Interpolator interpolator) {
484             this.interpolator = interpolator;
485         }
486 
487         public abstract float getPageAlpha(int pageIndex);
488     }
489 
490     /**
491      * Provider for the translation and animation interpolation of workspace pages.
492      */
493     public abstract static class PageTranslationProvider {
494 
495         public final Interpolator interpolator;
496 
497         public PageTranslationProvider(Interpolator interpolator) {
498             this.interpolator = interpolator;
499         }
500 
501         /**
502          * Gets the translation of the workspace page at the provided page index.
503          */
504         public abstract float getPageTranslation(int pageIndex);
505     }
506 
507     public static class ScaleAndTranslation {
508         public float scale;
509         public float translationX;
510         public float translationY;
511 
512         public ScaleAndTranslation(float scale, float translationX, float translationY) {
513             this.scale = scale;
514             this.translationX = translationX;
515             this.translationY = translationY;
516         }
517     }
518 }
519