1 /*
2  * Copyright (C) 2020 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.uioverrides.states;
17 
18 import static android.view.View.VISIBLE;
19 
20 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
21 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
22 import static com.android.launcher3.LauncherState.HINT_STATE;
23 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
24 import static com.android.launcher3.LauncherState.NORMAL;
25 import static com.android.launcher3.LauncherState.OVERVIEW;
26 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
27 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
28 import static com.android.launcher3.anim.Interpolators.ACCEL;
29 import static com.android.launcher3.anim.Interpolators.DEACCEL;
30 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
31 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
32 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
33 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
34 import static com.android.launcher3.anim.Interpolators.INSTANT;
35 import static com.android.launcher3.anim.Interpolators.LINEAR;
36 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
37 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
38 import static com.android.launcher3.anim.Interpolators.clampToProgress;
39 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
40 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
41 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
42 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
43 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
44 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
45 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
46 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
47 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
48 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
49 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
50 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
51 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
52 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
53 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
54 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
55 
56 import android.animation.Animator;
57 import android.animation.AnimatorSet;
58 import android.animation.ValueAnimator;
59 import android.view.View;
60 import android.view.animation.Interpolator;
61 
62 import com.android.launcher3.CellLayout;
63 import com.android.launcher3.Hotseat;
64 import com.android.launcher3.Launcher;
65 import com.android.launcher3.LauncherState;
66 import com.android.launcher3.LauncherState.ScaleAndTranslation;
67 import com.android.launcher3.Workspace;
68 import com.android.launcher3.allapps.AllAppsContainerView;
69 import com.android.launcher3.allapps.AllAppsTransitionController;
70 import com.android.launcher3.statemanager.StateManager;
71 import com.android.launcher3.states.StateAnimationConfig;
72 import com.android.launcher3.uioverrides.QuickstepLauncher;
73 import com.android.quickstep.SysUINavigationMode;
74 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
75 import com.android.quickstep.views.RecentsView;
76 
77 /**
78  * Animation factory for quickstep specific transitions
79  */
80 public class QuickstepAtomicAnimationFactory extends
81         RecentsAtomicAnimationFactory<Launcher, LauncherState> {
82 
83     // Scale recents takes before animating in
84     private static final float RECENTS_PREPARE_SCALE = 1.33f;
85 
86     public static final int INDEX_SHELF_ANIM = RecentsAtomicAnimationFactory.NEXT_INDEX + 0;
87     public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM =
88             RecentsAtomicAnimationFactory.NEXT_INDEX + 1;
89 
90     private static final int MY_ANIM_COUNT = 2;
91     protected static final int NEXT_INDEX = RecentsAtomicAnimationFactory.NEXT_INDEX
92             + MY_ANIM_COUNT;
93 
94     // Due to use of physics, duration may differ between devices so we need to calculate and
95     // cache the value.
96     private int mHintToNormalDuration = -1;
97 
98     public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
99 
QuickstepAtomicAnimationFactory(QuickstepLauncher activity)100     public QuickstepAtomicAnimationFactory(QuickstepLauncher activity) {
101         super(activity, MY_ANIM_COUNT);
102     }
103 
104     @Override
createStateElementAnimation(int index, float... values)105     public Animator createStateElementAnimation(int index, float... values) {
106         switch (index) {
107             case INDEX_SHELF_ANIM: {
108                 AllAppsTransitionController aatc = mActivity.getAllAppsController();
109                 Animator springAnim = aatc.createSpringAnimation(values);
110 
111                 if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
112                     // Translate hotseat with the shelf until reaching overview.
113                     float overviewProgress = OVERVIEW.getVerticalProgress(mActivity);
114                     ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mActivity);
115                     float shiftRange = aatc.getShiftRange();
116                     if (values.length == 1) {
117                         values = new float[] {aatc.getProgress(), values[0]};
118                     }
119                     ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
120                     hotseatAnim.addUpdateListener(anim -> {
121                         float progress = (Float) anim.getAnimatedValue();
122                         if (progress >= overviewProgress || mActivity.isInState(BACKGROUND_APP)) {
123                             float hotseatShift = (progress - overviewProgress) * shiftRange;
124                             mActivity.getHotseat().setTranslationY(hotseatShift + sat.translationY);
125                         }
126                     });
127                     hotseatAnim.setInterpolator(LINEAR);
128                     hotseatAnim.setDuration(springAnim.getDuration());
129 
130                     AnimatorSet anim = new AnimatorSet();
131                     anim.play(hotseatAnim);
132                     anim.play(springAnim);
133                     return anim;
134                 }
135 
136                 return springAnim;
137             }
138             case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
139                 StateAnimationConfig config = new StateAnimationConfig();
140                 config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
141 
142                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
143                 config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
144                 if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
145                     config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
146                     config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
147                 }
148 
149                 StateManager<LauncherState> stateManager = mActivity.getStateManager();
150                 return stateManager.createAtomicAnimation(
151                         stateManager.getCurrentStableState(), OVERVIEW, config);
152             }
153             default:
154                 return super.createStateElementAnimation(index, values);
155         }
156     }
157 
158     @Override
prepareForAtomicAnimation(LauncherState fromState, LauncherState toState, StateAnimationConfig config)159     public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
160             StateAnimationConfig config) {
161         if (toState == NORMAL && fromState == OVERVIEW) {
162             config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
163             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
164             config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
165             config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
166             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
167             config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
168             Workspace workspace = mActivity.getWorkspace();
169 
170             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
171             boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
172             if (isWorkspaceVisible) {
173                 CellLayout currentChild = (CellLayout) workspace.getChildAt(
174                         workspace.getCurrentPage());
175                 isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
176                         && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
177             }
178             if (!isWorkspaceVisible) {
179                 workspace.setScaleX(0.92f);
180                 workspace.setScaleY(0.92f);
181             }
182             Hotseat hotseat = mActivity.getHotseat();
183             boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
184             if (!isHotseatVisible) {
185                 hotseat.setScaleX(0.92f);
186                 hotseat.setScaleY(0.92f);
187                 if (ENABLE_OVERVIEW_ACTIONS.get()) {
188                     AllAppsContainerView qsbContainer = mActivity.getAppsView();
189                     View qsb = qsbContainer.getSearchView();
190                     boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
191                     if (!qsbVisible) {
192                         qsbContainer.setScaleX(0.92f);
193                         qsbContainer.setScaleY(0.92f);
194                     }
195                 }
196             }
197         } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) {
198             // Keep fully visible until the very end (when overview is offscreen) to make invisible.
199             config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
200         } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) {
201             config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
202             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
203             config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
204         } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
205             if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
206                 config.setInterpolator(ANIM_WORKSPACE_SCALE,
207                         fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
208                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
209             } else {
210                 config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
211 
212                 // Scale up the recents, if it is not coming from the side
213                 RecentsView overview = mActivity.getOverviewPanel();
214                 if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
215                     SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
216                 }
217             }
218             config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
219             config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
220             config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
221             config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
222             Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
223                     && removeShelfFromOverview(mActivity)
224                     ? OVERSHOOT_1_2
225                     : OVERSHOOT_1_7;
226             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
227             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
228             config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
229         } else if (fromState == HINT_STATE && toState == NORMAL) {
230             config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
231             if (mHintToNormalDuration == -1) {
232                 ValueAnimator va = getSpringScaleAnimator(mActivity, mActivity.getWorkspace(),
233                         toState.getWorkspaceScaleAndTranslation(mActivity).scale);
234                 mHintToNormalDuration = (int) va.getDuration();
235             }
236             config.duration = Math.max(config.duration, mHintToNormalDuration);
237         }
238     }
239 }
240