• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.quickstep.util;
17 
18 import static com.android.app.animation.Interpolators.DECELERATE;
19 import static com.android.app.animation.Interpolators.LINEAR;
20 import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
21 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
22 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
23 
24 import android.animation.AnimatorSet;
25 import android.animation.TimeInterpolator;
26 import android.content.Context;
27 import android.graphics.Matrix;
28 import android.graphics.PointF;
29 import android.graphics.Rect;
30 import android.graphics.RectF;
31 import android.util.FloatProperty;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.launcher3.DeviceProfile;
36 import com.android.launcher3.Launcher;
37 import com.android.launcher3.LauncherPrefs;
38 import com.android.launcher3.LauncherState;
39 import com.android.launcher3.Utilities;
40 import com.android.launcher3.anim.AnimatorPlaybackController;
41 import com.android.launcher3.anim.PendingAnimation;
42 import com.android.launcher3.statemanager.StateManager;
43 import com.android.launcher3.statemanager.StatefulActivity;
44 import com.android.launcher3.states.StateAnimationConfig;
45 import com.android.launcher3.touch.AllAppsSwipeController;
46 import com.android.launcher3.touch.PagedOrientationHandler;
47 import com.android.quickstep.views.RecentsView;
48 
49 /**
50  * Controls an animation that can go beyond progress = 1, at which point resistance should be
51  * applied. Internally, this is a wrapper around 2 {@link AnimatorPlaybackController}s, one that
52  * runs from progress 0 to 1 like normal, then one that seamlessly continues that animation but
53  * starts applying resistance as well.
54  */
55 public class AnimatorControllerWithResistance {
56 
57     private enum RecentsResistanceParams {
58         FROM_APP(0.75f, 0.5f, 1f, false),
59         FROM_APP_TO_ALL_APPS(1f, 0.6f, 0.8f, false),
60         FROM_APP_TABLET(1f, 0.7f, 1f, true),
61         FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false),
62         FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
63 
RecentsResistanceParams(float scaleStartResist, float scaleMaxResist, float translationFactor, boolean stopScalingAtTop)64         RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
65                 float translationFactor, boolean stopScalingAtTop) {
66             this.scaleStartResist = scaleStartResist;
67             this.scaleMaxResist = scaleMaxResist;
68             this.translationFactor = translationFactor;
69             this.stopScalingAtTop = stopScalingAtTop;
70         }
71 
72         /**
73          * Start slowing down the rate of scaling down when recents view is smaller than this scale.
74          */
75         public final float scaleStartResist;
76 
77         /**
78          * Recents view will reach this scale at the very end of the drag.
79          */
80         public final float scaleMaxResist;
81 
82         /**
83          * How much translation to apply to RecentsView when the drag reaches the top of the screen,
84          * where 0 will keep it centered and 1 will have it barely touch the top of the screen.
85          */
86         public final float translationFactor;
87 
88         /**
89          * Whether to end scaling effect when the scaled down version of TaskView's top reaches the
90          * non-scaled version of TaskView's top.
91          */
92         public final boolean stopScalingAtTop;
93     }
94 
95     private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DECELERATE;
96     private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
97 
98     private static final Rect TEMP_RECT = new Rect();
99 
100     private final AnimatorPlaybackController mNormalController;
101     private final AnimatorPlaybackController mResistanceController;
102 
103     // Initialize to -1 so the first 0 gets applied.
104     private float mLastNormalProgress = -1;
105     private float mLastResistProgress;
106 
AnimatorControllerWithResistance(AnimatorPlaybackController normalController, AnimatorPlaybackController resistanceController)107     public AnimatorControllerWithResistance(AnimatorPlaybackController normalController,
108             AnimatorPlaybackController resistanceController) {
109         mNormalController = normalController;
110         mResistanceController = resistanceController;
111     }
112 
getNormalController()113     public AnimatorPlaybackController getNormalController() {
114         return mNormalController;
115     }
116 
117     /**
118      * Applies the current progress of the animation.
119      * @param progress From 0 to maxProgress, where 1 is the target we are animating towards.
120      * @param maxProgress > 1, this is where the resistance will be applied.
121      */
setProgress(float progress, float maxProgress)122     public void setProgress(float progress, float maxProgress) {
123         float normalProgress = Utilities.boundToRange(progress, 0, 1);
124         if (normalProgress != mLastNormalProgress) {
125             mLastNormalProgress = normalProgress;
126             mNormalController.setPlayFraction(normalProgress);
127         }
128         if (maxProgress <= 1) {
129             return;
130         }
131         float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress);
132         if (resistProgress != mLastResistProgress) {
133             mLastResistProgress = resistProgress;
134             mResistanceController.setPlayFraction(resistProgress);
135         }
136     }
137 
138     /**
139      * Applies resistance to recents when swiping up past its target position.
140      * @param normalController The controller to run from 0 to 1 before this resistance applies.
141      * @param context Used to compute start and end values.
142      * @param recentsOrientedState Used to compute start and end values.
143      * @param dp Used to compute start and end values.
144      * @param scaleTarget The target for the scaleProperty.
145      * @param scaleProperty Animate the value to change the scale of the window/recents view.
146      * @param translationTarget The target for the translationProperty.
147      * @param translationProperty Animate the value to change the translation of the recents view.
148      */
createForRecents( AnimatorPlaybackController normalController, Context context, RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty)149     public static <SCALE, TRANSLATION> AnimatorControllerWithResistance createForRecents(
150             AnimatorPlaybackController normalController, Context context,
151             RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
152             FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
153             FloatProperty<TRANSLATION> translationProperty) {
154 
155         RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
156                 scaleProperty, translationTarget, translationProperty);
157         PendingAnimation resistAnim = createRecentsResistanceAnim(params);
158 
159         // Apply All Apps animation during the resistance animation.
160         if (recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()) {
161             StatefulActivity activity =
162                     recentsOrientedState.getActivityInterface().getCreatedActivity();
163             if (activity != null) {
164                 StateManager<LauncherState> stateManager = activity.getStateManager();
165                 if (stateManager.isInStableState(LauncherState.BACKGROUND_APP)
166                         && stateManager.isInTransition()) {
167 
168                     // Calculate the resistance progress threshold where All Apps will trigger.
169                     float threshold = getAllAppsThreshold(context, recentsOrientedState, dp);
170 
171                     StateAnimationConfig config = new StateAnimationConfig();
172                     AllAppsSwipeController.applyOverviewToAllAppsAnimConfig(dp, config, threshold);
173                     AnimatorSet allAppsAnimator = stateManager.createAnimationToNewWorkspace(
174                             LauncherState.ALL_APPS, config).getTarget();
175                     resistAnim.add(allAppsAnimator);
176                 }
177             }
178         }
179 
180         AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
181         return new AnimatorControllerWithResistance(normalController, resistanceController);
182     }
183 
getAllAppsThreshold(Context context, RecentsOrientedState recentsOrientedState, DeviceProfile dp)184     private static float getAllAppsThreshold(Context context,
185             RecentsOrientedState recentsOrientedState, DeviceProfile dp) {
186         int transitionDragLength =
187                 recentsOrientedState.getActivityInterface().getSwipeUpDestinationAndLength(
188                         dp, context, TEMP_RECT,
189                         recentsOrientedState.getOrientationHandler());
190         float dragLengthFactor = (float) dp.heightPx / transitionDragLength;
191         // -1s are because 0-1 is reserved for the normal transition.
192         float threshold = LauncherPrefs.get(context).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
193         return (threshold - 1) / (dragLengthFactor - 1);
194     }
195 
196     /**
197      * Creates the resistance animation for {@link #createForRecents}, or can be used separately
198      * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
199      */
createRecentsResistanceAnim( RecentsParams<SCALE, TRANSLATION> params)200     public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
201             RecentsParams<SCALE, TRANSLATION> params) {
202         Rect startRect = new Rect();
203         PagedOrientationHandler orientationHandler = params.recentsOrientedState
204                 .getOrientationHandler();
205         params.recentsOrientedState.getActivityInterface()
206                 .calculateTaskSize(params.context, params.dp, startRect, orientationHandler);
207         long distanceToCover = startRect.bottom;
208         PendingAnimation resistAnim = params.resistAnim != null
209                 ? params.resistAnim
210                 : new PendingAnimation(distanceToCover * 2);
211 
212         PointF pivot = new PointF();
213         float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
214                 startRect, params.dp, pivot);
215 
216         // Compute where the task view would be based on the end scale.
217         RectF endRectF = new RectF(startRect);
218         Matrix temp = new Matrix();
219         temp.setScale(params.resistanceParams.scaleMaxResist,
220                 params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
221         temp.mapRect(endRectF);
222         // Translate such that the task view touches the top of the screen when drag does.
223         float endTranslation = endRectF.top
224                 * orientationHandler.getSecondaryTranslationDirectionFactor()
225                 * params.resistanceParams.translationFactor;
226         resistAnim.addFloat(params.translationTarget, params.translationProperty,
227                 params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
228 
229         float prevScaleRate = (fullscreenScale - params.startScale)
230                 / (params.dp.heightPx - startRect.bottom);
231         // This is what the scale would be at the end of the drag if we didn't apply resistance.
232         float endScale = params.startScale - prevScaleRate * distanceToCover;
233         // Create an interpolator that resists the scale so the scale doesn't get smaller than
234         // RECENTS_SCALE_MAX_RESIST.
235         float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
236                 params.startScale, endScale);
237         float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
238                 params.startScale, endScale);
239         float stopResist =
240                 params.resistanceParams.stopScalingAtTop ? 1f - startRect.top / endRectF.top : 1f;
241         final TimeInterpolator scaleInterpolator = t -> {
242             if (t < startResist) {
243                 return t;
244             }
245             if (t > stopResist) {
246                 return maxResist;
247             }
248             float resistProgress = Utilities.getProgress(t, startResist, stopResist);
249             resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
250             return startResist + resistProgress * (maxResist - startResist);
251         };
252         resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
253                 scaleInterpolator);
254 
255         return resistAnim;
256     }
257 
258     /**
259      * Helper method to update or create a PendingAnimation suitable for animating
260      * a RecentsView interaction that started from the overview state.
261      */
createRecentsResistanceFromOverviewAnim( Launcher launcher, @Nullable PendingAnimation resistanceAnim)262     public static PendingAnimation createRecentsResistanceFromOverviewAnim(
263             Launcher launcher, @Nullable PendingAnimation resistanceAnim) {
264         RecentsView recentsView = launcher.getOverviewPanel();
265         RecentsParams params = new RecentsParams(launcher, recentsView.getPagedViewOrientedState(),
266                 launcher.getDeviceProfile(), recentsView, RECENTS_SCALE_PROPERTY, recentsView,
267                 TASK_SECONDARY_TRANSLATION)
268                 .setResistAnim(resistanceAnim)
269                 .setResistanceParams(RecentsResistanceParams.FROM_OVERVIEW)
270                 .setStartScale(recentsView.getScaleX());
271         return createRecentsResistanceAnim(params);
272     }
273 
274     /**
275      * Params to compute resistance when scaling/translating recents.
276      */
277     private static class RecentsParams<SCALE, TRANSLATION> {
278         // These are all required and can't have default values, hence are final.
279         public final Context context;
280         public final RecentsOrientedState recentsOrientedState;
281         public final DeviceProfile dp;
282         public final SCALE scaleTarget;
283         public final FloatProperty<SCALE> scaleProperty;
284         public final TRANSLATION translationTarget;
285         public final FloatProperty<TRANSLATION> translationProperty;
286 
287         // These are not required, or can have a default value that is generally correct.
288         @Nullable public PendingAnimation resistAnim = null;
289         public RecentsResistanceParams resistanceParams;
290         public float startScale = 1f;
291         public float startTranslation = 0f;
292 
RecentsParams(Context context, RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty)293         private RecentsParams(Context context, RecentsOrientedState recentsOrientedState,
294                 DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty,
295                 TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty) {
296             this.context = context;
297             this.recentsOrientedState = recentsOrientedState;
298             this.dp = dp;
299             this.scaleTarget = scaleTarget;
300             this.scaleProperty = scaleProperty;
301             this.translationTarget = translationTarget;
302             this.translationProperty = translationProperty;
303             if (dp.isTablet) {
304                 resistanceParams =
305                         recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
306                                 ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS_TABLET
307                                 : RecentsResistanceParams.FROM_APP_TABLET;
308             } else {
309                 resistanceParams =
310                         recentsOrientedState.getActivityInterface().allowAllAppsFromOverview()
311                                 ? RecentsResistanceParams.FROM_APP_TO_ALL_APPS
312                                 : RecentsResistanceParams.FROM_APP;
313             }
314         }
315 
setResistAnim(PendingAnimation resistAnim)316         private RecentsParams setResistAnim(PendingAnimation resistAnim) {
317             this.resistAnim = resistAnim;
318             return this;
319         }
320 
setResistanceParams(RecentsResistanceParams resistanceParams)321         private RecentsParams setResistanceParams(RecentsResistanceParams resistanceParams) {
322             this.resistanceParams = resistanceParams;
323             return this;
324         }
325 
setStartScale(float startScale)326         private RecentsParams setStartScale(float startScale) {
327             this.startScale = startScale;
328             return this;
329         }
330 
setStartTranslation(float startTranslation)331         private RecentsParams setStartTranslation(float startTranslation) {
332             this.startTranslation = startTranslation;
333             return this;
334         }
335     }
336 }
337