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