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.launcher3.anim.Interpolators.DEACCEL; 19 import static com.android.launcher3.anim.Interpolators.LINEAR; 20 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; 21 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; 22 23 import android.animation.TimeInterpolator; 24 import android.content.Context; 25 import android.graphics.Matrix; 26 import android.graphics.PointF; 27 import android.graphics.Rect; 28 import android.graphics.RectF; 29 import android.util.FloatProperty; 30 31 import androidx.annotation.Nullable; 32 33 import com.android.launcher3.DeviceProfile; 34 import com.android.launcher3.Launcher; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.anim.AnimatorPlaybackController; 37 import com.android.launcher3.anim.PendingAnimation; 38 import com.android.launcher3.touch.PagedOrientationHandler; 39 import com.android.quickstep.LauncherActivityInterface; 40 import com.android.quickstep.views.RecentsView; 41 42 /** 43 * Controls an animation that can go beyond progress = 1, at which point resistance should be 44 * applied. Internally, this is a wrapper around 2 {@link AnimatorPlaybackController}s, one that 45 * runs from progress 0 to 1 like normal, then one that seamlessly continues that animation but 46 * starts applying resistance as well. 47 */ 48 public class AnimatorControllerWithResistance { 49 50 private enum RecentsResistanceParams { 51 FROM_APP(0.75f, 0.5f, 1f), 52 FROM_OVERVIEW(1f, 0.75f, 0.5f); 53 RecentsResistanceParams(float scaleStartResist, float scaleMaxResist, float translationFactor)54 RecentsResistanceParams(float scaleStartResist, float scaleMaxResist, 55 float translationFactor) { 56 this.scaleStartResist = scaleStartResist; 57 this.scaleMaxResist = scaleMaxResist; 58 this.translationFactor = translationFactor; 59 } 60 61 /** 62 * Start slowing down the rate of scaling down when recents view is smaller than this scale. 63 */ 64 public final float scaleStartResist; 65 66 /** 67 * Recents view will reach this scale at the very end of the drag. 68 */ 69 public final float scaleMaxResist; 70 71 /** 72 * How much translation to apply to RecentsView when the drag reaches the top of the screen, 73 * where 0 will keep it centered and 1 will have it barely touch the top of the screen. 74 */ 75 public final float translationFactor; 76 } 77 78 private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL; 79 private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR; 80 81 private final AnimatorPlaybackController mNormalController; 82 private final AnimatorPlaybackController mResistanceController; 83 84 // Initialize to -1 so the first 0 gets applied. 85 private float mLastNormalProgress = -1; 86 private float mLastResistProgress; 87 AnimatorControllerWithResistance(AnimatorPlaybackController normalController, AnimatorPlaybackController resistanceController)88 public AnimatorControllerWithResistance(AnimatorPlaybackController normalController, 89 AnimatorPlaybackController resistanceController) { 90 mNormalController = normalController; 91 mResistanceController = resistanceController; 92 } 93 getNormalController()94 public AnimatorPlaybackController getNormalController() { 95 return mNormalController; 96 } 97 98 /** 99 * Applies the current progress of the animation. 100 * @param progress From 0 to maxProgress, where 1 is the target we are animating towards. 101 * @param maxProgress > 1, this is where the resistance will be applied. 102 */ setProgress(float progress, float maxProgress)103 public void setProgress(float progress, float maxProgress) { 104 float normalProgress = Utilities.boundToRange(progress, 0, 1); 105 if (normalProgress != mLastNormalProgress) { 106 mLastNormalProgress = normalProgress; 107 mNormalController.setPlayFraction(normalProgress); 108 } 109 if (maxProgress <= 1) { 110 return; 111 } 112 float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress); 113 if (resistProgress != mLastResistProgress) { 114 mLastResistProgress = resistProgress; 115 mResistanceController.setPlayFraction(resistProgress); 116 } 117 } 118 119 /** 120 * Applies resistance to recents when swiping up past its target position. 121 * @param normalController The controller to run from 0 to 1 before this resistance applies. 122 * @param context Used to compute start and end values. 123 * @param recentsOrientedState Used to compute start and end values. 124 * @param dp Used to compute start and end values. 125 * @param scaleTarget The target for the scaleProperty. 126 * @param scaleProperty Animate the value to change the scale of the window/recents view. 127 * @param translationTarget The target for the translationProperty. 128 * @param translationProperty Animate the value to change the translation of the recents view. 129 */ createForRecents( AnimatorPlaybackController normalController, Context context, RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty)130 public static <SCALE, TRANSLATION> AnimatorControllerWithResistance createForRecents( 131 AnimatorPlaybackController normalController, Context context, 132 RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, 133 FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget, 134 FloatProperty<TRANSLATION> translationProperty) { 135 136 RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget, 137 scaleProperty, translationTarget, translationProperty); 138 PendingAnimation resistAnim = createRecentsResistanceAnim(params); 139 140 AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController(); 141 return new AnimatorControllerWithResistance(normalController, resistanceController); 142 } 143 144 /** 145 * Creates the resistance animation for {@link #createForRecents}, or can be used separately 146 * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}. 147 */ createRecentsResistanceAnim( RecentsParams<SCALE, TRANSLATION> params)148 public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim( 149 RecentsParams<SCALE, TRANSLATION> params) { 150 Rect startRect = new Rect(); 151 PagedOrientationHandler orientationHandler = params.recentsOrientedState 152 .getOrientationHandler(); 153 LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect, 154 orientationHandler); 155 long distanceToCover = startRect.bottom; 156 PendingAnimation resistAnim = params.resistAnim != null 157 ? params.resistAnim 158 : new PendingAnimation(distanceToCover * 2); 159 160 PointF pivot = new PointF(); 161 float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot( 162 startRect, params.dp, pivot); 163 float prevScaleRate = (fullscreenScale - params.startScale) 164 / (params.dp.heightPx - startRect.bottom); 165 // This is what the scale would be at the end of the drag if we didn't apply resistance. 166 float endScale = params.startScale - prevScaleRate * distanceToCover; 167 // Create an interpolator that resists the scale so the scale doesn't get smaller than 168 // RECENTS_SCALE_MAX_RESIST. 169 float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist, 170 params.startScale, endScale); 171 float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist, 172 params.startScale, endScale); 173 final TimeInterpolator scaleInterpolator = t -> { 174 if (t < startResist) { 175 return t; 176 } 177 float resistProgress = Utilities.getProgress(t, startResist, 1); 178 resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress); 179 return startResist + resistProgress * (maxResist - startResist); 180 }; 181 resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale, 182 scaleInterpolator); 183 184 // Compute where the task view would be based on the end scale. 185 RectF endRectF = new RectF(startRect); 186 Matrix temp = new Matrix(); 187 temp.setScale(params.resistanceParams.scaleMaxResist, 188 params.resistanceParams.scaleMaxResist, pivot.x, pivot.y); 189 temp.mapRect(endRectF); 190 // Translate such that the task view touches the top of the screen when drag does. 191 float endTranslation = endRectF.top 192 * orientationHandler.getSecondaryTranslationDirectionFactor() 193 * params.resistanceParams.translationFactor; 194 resistAnim.addFloat(params.translationTarget, params.translationProperty, 195 params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR); 196 197 return resistAnim; 198 } 199 200 /** 201 * Helper method to update or create a PendingAnimation suitable for animating 202 * a RecentsView interaction that started from the overview state. 203 */ createRecentsResistanceFromOverviewAnim( Launcher launcher, @Nullable PendingAnimation resistanceAnim)204 public static PendingAnimation createRecentsResistanceFromOverviewAnim( 205 Launcher launcher, @Nullable PendingAnimation resistanceAnim) { 206 RecentsView recentsView = launcher.getOverviewPanel(); 207 RecentsParams params = new RecentsParams(launcher, recentsView.getPagedViewOrientedState(), 208 launcher.getDeviceProfile(), recentsView, RECENTS_SCALE_PROPERTY, recentsView, 209 TASK_SECONDARY_TRANSLATION) 210 .setResistAnim(resistanceAnim) 211 .setResistanceParams(RecentsResistanceParams.FROM_OVERVIEW) 212 .setStartScale(recentsView.getScaleX()); 213 return createRecentsResistanceAnim(params); 214 } 215 216 /** 217 * Params to compute resistance when scaling/translating recents. 218 */ 219 private static class RecentsParams<SCALE, TRANSLATION> { 220 // These are all required and can't have default values, hence are final. 221 public final Context context; 222 public final RecentsOrientedState recentsOrientedState; 223 public final DeviceProfile dp; 224 public final SCALE scaleTarget; 225 public final FloatProperty<SCALE> scaleProperty; 226 public final TRANSLATION translationTarget; 227 public final FloatProperty<TRANSLATION> translationProperty; 228 229 // These are not required, or can have a default value that is generally correct. 230 @Nullable public PendingAnimation resistAnim = null; 231 public RecentsResistanceParams resistanceParams = RecentsResistanceParams.FROM_APP; 232 public float startScale = 1f; 233 public float startTranslation = 0f; 234 RecentsParams(Context context, RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty)235 private RecentsParams(Context context, RecentsOrientedState recentsOrientedState, 236 DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty, 237 TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty) { 238 this.context = context; 239 this.recentsOrientedState = recentsOrientedState; 240 this.dp = dp; 241 this.scaleTarget = scaleTarget; 242 this.scaleProperty = scaleProperty; 243 this.translationTarget = translationTarget; 244 this.translationProperty = translationProperty; 245 } 246 setResistAnim(PendingAnimation resistAnim)247 private RecentsParams setResistAnim(PendingAnimation resistAnim) { 248 this.resistAnim = resistAnim; 249 return this; 250 } 251 setResistanceParams(RecentsResistanceParams resistanceParams)252 private RecentsParams setResistanceParams(RecentsResistanceParams resistanceParams) { 253 this.resistanceParams = resistanceParams; 254 return this; 255 } 256 setStartScale(float startScale)257 private RecentsParams setStartScale(float startScale) { 258 this.startScale = startScale; 259 return this; 260 } 261 setStartTranslation(float startTranslation)262 private RecentsParams setStartTranslation(float startTranslation) { 263 this.startTranslation = startTranslation; 264 return this; 265 } 266 } 267 } 268