1 /*
2  * Copyright (C) 2019 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.quickstep.util;
17 
18 import static com.android.app.animation.Interpolators.LINEAR;
19 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
20 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
21 import static com.android.launcher3.LauncherState.NORMAL;
22 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
23 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
24 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
25 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
26 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
27 
28 import android.animation.Animator;
29 import android.animation.AnimatorListenerAdapter;
30 import android.animation.AnimatorSet;
31 import android.animation.ObjectAnimator;
32 import android.animation.ValueAnimator;
33 import android.view.View;
34 import android.view.ViewGroup;
35 
36 import androidx.annotation.Nullable;
37 
38 import com.android.launcher3.CellLayout;
39 import com.android.launcher3.DeviceProfile;
40 import com.android.launcher3.Hotseat;
41 import com.android.launcher3.LauncherState;
42 import com.android.launcher3.QuickstepTransitionManager;
43 import com.android.launcher3.R;
44 import com.android.launcher3.ShortcutAndWidgetContainer;
45 import com.android.launcher3.Workspace;
46 import com.android.launcher3.anim.PendingAnimation;
47 import com.android.launcher3.anim.SpringAnimationBuilder;
48 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
49 import com.android.launcher3.statehandlers.DepthController;
50 import com.android.launcher3.states.StateAnimationConfig;
51 import com.android.launcher3.uioverrides.QuickstepLauncher;
52 import com.android.launcher3.util.DynamicResource;
53 import com.android.quickstep.views.RecentsView;
54 import com.android.systemui.plugins.ResourceProvider;
55 
56 /**
57  * Creates an animation where all the workspace items are moved into their final location,
58  * staggered row by row from the bottom up.
59  * This is used in conjunction with the swipe up to home animation.
60  */
61 public class StaggeredWorkspaceAnim {
62 
63     private static final int APP_CLOSE_ROW_START_DELAY_MS = 10;
64     // Should be used for animations running alongside this StaggeredWorkspaceAnim.
65     public static final int DURATION_MS = 250;
66     public static final int DURATION_TASKBAR_MS =
67             QuickstepTransitionManager.getTaskbarToHomeDuration();
68 
69     private static final float MAX_VELOCITY_PX_PER_S = 22f;
70 
71     private final float mVelocity;
72     private final float mSpringTransY;
73 
74     private final AnimatorSet mAnimators = new AnimatorSet();
75     private final @Nullable View mIgnoredView;
76 
StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, boolean animateOverviewScrim, @Nullable View ignoredView)77     public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
78             boolean animateOverviewScrim, @Nullable View ignoredView) {
79         this(launcher, velocity, animateOverviewScrim, ignoredView, true);
80     }
81 
StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace)82     public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
83             boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) {
84         prepareToAnimate(launcher, animateOverviewScrim);
85 
86         mIgnoredView = ignoredView;
87         mVelocity = velocity;
88 
89         // Scale the translationY based on the initial velocity to better sync the workspace items
90         // with the floating view.
91         float transFactor = 0.2f + 0.9f * Math.abs(velocity) / MAX_VELOCITY_PX_PER_S;
92         mSpringTransY = transFactor * launcher.getResources()
93                 .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);
94 
95         DeviceProfile grid = launcher.getDeviceProfile();
96         long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS;
97         if (staggerWorkspace) {
98             Workspace<?> workspace = launcher.getWorkspace();
99             Hotseat hotseat = launcher.getHotseat();
100 
101             boolean staggerHotseat = !grid.isVerticalBarLayout() && !grid.isTaskbarPresent;
102             boolean staggerQsb =
103                     !grid.isVerticalBarLayout() && !(grid.isTaskbarPresent && grid.isQsbInline);
104             int totalRows = grid.inv.numRows + (staggerHotseat ? 1 : 0) + (staggerQsb ? 1 : 0);
105 
106             // Add animation for all the visible workspace pages
107             workspace.forEachVisiblePage(
108                     page -> addAnimationForPage((CellLayout) page, totalRows, duration));
109 
110             boolean workspaceClipChildren = workspace.getClipChildren();
111             boolean workspaceClipToPadding = workspace.getClipToPadding();
112             boolean hotseatClipChildren = hotseat.getClipChildren();
113             boolean hotseatClipToPadding = hotseat.getClipToPadding();
114 
115             workspace.setClipChildren(false);
116             workspace.setClipToPadding(false);
117             hotseat.setClipChildren(false);
118             hotseat.setClipToPadding(false);
119 
120             // Set up springs for the hotseat and qsb.
121             ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
122             if (grid.isVerticalBarLayout()) {
123                 for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
124                     View child = hotseatIcons.getChildAt(i);
125                     CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
126                     addStaggeredAnimationForView(child, lp.getCellY() + 1,
127                             totalRows, duration);
128                 }
129             } else {
130                 final int hotseatRow, qsbRow;
131                 if (grid.isTaskbarPresent) {
132                     if (grid.isQsbInline) {
133                         qsbRow = grid.inv.numRows + 1;
134                         hotseatRow = grid.inv.numRows + 1;
135                     } else {
136                         qsbRow = grid.inv.numRows + 1;
137                         hotseatRow = grid.inv.numRows + 2;
138                     }
139                 } else {
140                     hotseatRow = grid.inv.numRows + 1;
141                     qsbRow = grid.inv.numRows + 2;
142                 }
143 
144                 // Do not stagger hotseat as a whole when taskbar is present, and stagger QSB only
145                 // if it's not inline.
146                 if (staggerHotseat) {
147                     for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
148                         View child = hotseatIcons.getChildAt(i);
149                         addStaggeredAnimationForView(child, hotseatRow, totalRows, duration);
150                     }
151                 }
152                 if (staggerQsb) {
153                     addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows, duration);
154                 }
155             }
156 
157             mAnimators.addListener(new AnimatorListenerAdapter() {
158                 @Override
159                 public void onAnimationEnd(Animator animation) {
160                     workspace.setClipChildren(workspaceClipChildren);
161                     workspace.setClipToPadding(workspaceClipToPadding);
162                     hotseat.setClipChildren(hotseatClipChildren);
163                     hotseat.setClipToPadding(hotseatClipToPadding);
164                 }
165             });
166         }
167 
168         launcher.pauseExpensiveViewUpdates();
169         mAnimators.addListener(forEndCallback(launcher::resumeExpensiveViewUpdates));
170 
171         if (animateOverviewScrim) {
172             PendingAnimation pendingAnimation = new PendingAnimation(duration);
173             launcher.getWorkspace().getStateTransitionAnimation()
174                     .setScrim(pendingAnimation, NORMAL, new StateAnimationConfig());
175             mAnimators.play(pendingAnimation.buildAnim());
176         }
177 
178         addDepthAnimationForState(launcher, NORMAL, duration);
179 
180         mAnimators.play(launcher.getRootView().getSysUiScrim().getSysUIMultiplier()
181                 .animateToValue(0f, 1f).setDuration(duration));
182     }
183 
addAnimationForPage(CellLayout page, int totalRows, long duration)184     private void addAnimationForPage(CellLayout page, int totalRows, long duration) {
185         ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets();
186 
187         boolean pageClipChildren = page.getClipChildren();
188         boolean pageClipToPadding = page.getClipToPadding();
189 
190         page.setClipChildren(false);
191         page.setClipToPadding(false);
192 
193         // Set up springs on workspace items.
194         for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
195             View child = itemsContainer.getChildAt(i);
196             CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
197             addStaggeredAnimationForView(child, lp.getCellY() + lp.cellVSpan,
198                     totalRows, duration);
199         }
200 
201         mAnimators.addListener(new AnimatorListenerAdapter() {
202             @Override
203             public void onAnimationEnd(Animator animation) {
204                 page.setClipChildren(pageClipChildren);
205                 page.setClipToPadding(pageClipToPadding);
206             }
207         });
208     }
209 
210     /**
211      * Setup workspace with 0 duration to prepare for our staggered animation.
212      */
prepareToAnimate(QuickstepLauncher launcher, boolean animateOverviewScrim)213     private void prepareToAnimate(QuickstepLauncher launcher, boolean animateOverviewScrim) {
214         StateAnimationConfig config = new StateAnimationConfig();
215         config.animFlags = SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER | SKIP_SCRIM;
216         config.duration = 0;
217         // setRecentsAttachedToAppWindow() will animate recents out.
218         launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
219 
220         // Stop scrolling so that it doesn't interfere with the translation offscreen.
221         launcher.<RecentsView>getOverviewPanel().forceFinishScroller();
222 
223         if (animateOverviewScrim) {
224             launcher.getWorkspace().getStateTransitionAnimation()
225                     .setScrim(NO_ANIM_PROPERTY_SETTER, BACKGROUND_APP, config);
226         }
227     }
228 
getAnimators()229     public AnimatorSet getAnimators() {
230         return mAnimators;
231     }
232 
addAnimatorListener(Animator.AnimatorListener listener)233     public StaggeredWorkspaceAnim addAnimatorListener(Animator.AnimatorListener listener) {
234         mAnimators.addListener(listener);
235         return this;
236     }
237 
238     /**
239      * Starts the animation.
240      */
start()241     public void start() {
242         mAnimators.start();
243     }
244 
245     /**
246      * Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row.
247      *
248      * @param v A view on the workspace.
249      * @param row The bottom-most row that contains the view.
250      * @param totalRows Total number of rows.
251      * @param duration duration of the animation
252      */
addStaggeredAnimationForView(View v, int row, int totalRows, long duration)253     private void addStaggeredAnimationForView(View v, int row, int totalRows, long duration) {
254         if (mIgnoredView != null && mIgnoredView == v) return;
255         // Invert the rows, because we stagger starting from the bottom of the screen.
256         int invertedRow = totalRows - row;
257         // Add 1 to the inverted row so that the bottom most row has a start delay.
258         long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS);
259 
260         v.setTranslationY(mSpringTransY);
261 
262         ResourceProvider rp = DynamicResource.provider(v.getContext());
263         float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
264         float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
265         float endTransY = 0;
266         float springVelocity = Math.abs(mVelocity) * Math.signum(endTransY - mSpringTransY);
267         ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext())
268                 .setStiffness(stiffness)
269                 .setDampingRatio(damping)
270                 .setMinimumVisibleChange(1f)
271                 .setStartValue(mSpringTransY)
272                 .setEndValue(endTransY)
273                 .setStartVelocity(springVelocity)
274                 .build(v, VIEW_TRANSLATE_Y);
275         springTransY.setStartDelay(startDelay);
276         springTransY.addListener(new AnimatorListenerAdapter() {
277             @Override
278             public void onAnimationEnd(Animator animation) {
279                 v.setTranslationY(0f);
280             }
281         });
282         mAnimators.play(springTransY);
283 
284         v.setAlpha(0);
285         ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
286         alpha.setInterpolator(LINEAR);
287         alpha.setDuration(duration);
288         alpha.setStartDelay(startDelay);
289         alpha.addListener(new AnimatorListenerAdapter() {
290             @Override
291             public void onAnimationEnd(Animator animation) {
292                 v.setAlpha(1f);
293             }
294         });
295         mAnimators.play(alpha);
296     }
297 
addDepthAnimationForState(QuickstepLauncher launcher, LauncherState state, long duration)298     private void addDepthAnimationForState(QuickstepLauncher launcher, LauncherState state,
299             long duration) {
300         PendingAnimation builder = new PendingAnimation(duration);
301         DepthController depthController = launcher.getDepthController();
302         depthController.setStateWithAnimation(state, new StateAnimationConfig(), builder);
303         mAnimators.play(builder.buildAnim());
304     }
305 }
306