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; 17 18 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; 19 import static com.android.launcher3.anim.Interpolators.DEACCEL; 20 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; 21 22 import android.animation.Animator; 23 import android.content.Context; 24 import android.graphics.Matrix; 25 import android.graphics.Matrix.ScaleToFit; 26 import android.graphics.Rect; 27 import android.graphics.RectF; 28 import android.view.animation.Interpolator; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.UiThread; 33 34 import com.android.launcher3.DeviceProfile; 35 import com.android.launcher3.Utilities; 36 import com.android.launcher3.anim.AnimationSuccessListener; 37 import com.android.launcher3.anim.AnimatorPlaybackController; 38 import com.android.launcher3.anim.PendingAnimation; 39 import com.android.launcher3.touch.PagedOrientationHandler; 40 import com.android.launcher3.views.FloatingIconView; 41 import com.android.quickstep.util.RectFSpringAnim; 42 import com.android.quickstep.util.TaskViewSimulator; 43 import com.android.quickstep.util.TransformParams; 44 import com.android.quickstep.util.TransformParams.BuilderProxy; 45 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 46 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; 47 48 public abstract class SwipeUpAnimationLogic { 49 50 protected static final Rect TEMP_RECT = new Rect(); 51 private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL; 52 53 protected DeviceProfile mDp; 54 55 protected final Context mContext; 56 protected final RecentsAnimationDeviceState mDeviceState; 57 protected final GestureState mGestureState; 58 protected final TaskViewSimulator mTaskViewSimulator; 59 60 protected final TransformParams mTransformParams; 61 62 // Shift in the range of [0, 1]. 63 // 0 => preview snapShot is completely visible, and hotseat is completely translated down 64 // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely 65 // visible. 66 protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift); 67 68 // The distance needed to drag to reach the task size in recents. 69 protected int mTransitionDragLength; 70 // How much further we can drag past recents, as a factor of mTransitionDragLength. 71 protected float mDragLengthFactor = 1; 72 // Start resisting when swiping past this factor of mTransitionDragLength. 73 private float mDragLengthFactorStartPullback = 1f; 74 // This is how far down we can scale down, where 0f is full screen and 1f is recents. 75 private float mDragLengthFactorMaxPullback = 1f; 76 77 protected AnimatorPlaybackController mWindowTransitionController; 78 SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState, TransformParams transformParams)79 public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, 80 GestureState gestureState, TransformParams transformParams) { 81 mContext = context; 82 mDeviceState = deviceState; 83 mGestureState = gestureState; 84 mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); 85 mTransformParams = transformParams; 86 87 mTaskViewSimulator.setLayoutRotation( 88 mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation()); 89 } 90 initTransitionEndpoints(DeviceProfile dp)91 protected void initTransitionEndpoints(DeviceProfile dp) { 92 mDp = dp; 93 94 mTaskViewSimulator.setDp(dp); 95 mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength( 96 dp, mContext, TEMP_RECT, 97 mTaskViewSimulator.getOrientationState().getOrientationHandler()); 98 99 if (mDeviceState.isFullyGesturalNavMode()) { 100 // We can drag all the way to the top of the screen. 101 mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; 102 103 float startScale = mTaskViewSimulator.getFullScreenScale(); 104 // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f. 105 mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale); 106 mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale); 107 } else { 108 mDragLengthFactor = 1; 109 mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1; 110 } 111 112 PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); 113 mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor); 114 mWindowTransitionController = pa.createPlaybackController(); 115 } 116 117 @UiThread updateDisplacement(float displacement)118 public void updateDisplacement(float displacement) { 119 // We are moving in the negative x/y direction 120 displacement = -displacement; 121 float shift; 122 if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { 123 shift = mDragLengthFactor; 124 } else { 125 float translation = Math.max(displacement, 0); 126 shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; 127 if (shift > mDragLengthFactorStartPullback) { 128 float pullbackProgress = Utilities.getProgress(shift, 129 mDragLengthFactorStartPullback, mDragLengthFactor); 130 pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress); 131 shift = mDragLengthFactorStartPullback + pullbackProgress 132 * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback); 133 } 134 } 135 136 mCurrentShift.updateValue(shift); 137 } 138 139 /** 140 * Called when the value of {@link #mCurrentShift} changes 141 */ 142 @UiThread updateFinalShift()143 public abstract void updateFinalShift(); 144 getOrientationHandler()145 protected PagedOrientationHandler getOrientationHandler() { 146 return mTaskViewSimulator.getOrientationState().getOrientationHandler(); 147 } 148 149 protected abstract class HomeAnimationFactory { 150 151 public FloatingIconView mIconView; 152 HomeAnimationFactory(@ullable FloatingIconView iconView)153 public HomeAnimationFactory(@Nullable FloatingIconView iconView) { 154 mIconView = iconView; 155 } 156 getWindowTargetRect()157 public @NonNull RectF getWindowTargetRect() { 158 PagedOrientationHandler orientationHandler = getOrientationHandler(); 159 DeviceProfile dp = mDp; 160 final int halfIconSize = dp.iconSizePx / 2; 161 float primaryDimension = orientationHandler 162 .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); 163 float secondaryDimension = orientationHandler 164 .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); 165 final float targetX = primaryDimension / 2f; 166 final float targetY = secondaryDimension - dp.hotseatBarSizePx; 167 // Fallback to animate to center of screen. 168 return new RectF(targetX - halfIconSize, targetY - halfIconSize, 169 targetX + halfIconSize, targetY + halfIconSize); 170 } 171 createActivityAnimationToHome()172 public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome(); 173 playAtomicAnimation(float velocity)174 public void playAtomicAnimation(float velocity) { 175 // No-op 176 } 177 } 178 179 /** 180 * Creates an animation that transforms the current app window into the home app. 181 * @param startProgress The progress of {@link #mCurrentShift} to start the window from. 182 * @param homeAnimationFactory The home animation factory. 183 */ createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)184 protected RectFSpringAnim createWindowAnimationToHome(float startProgress, 185 HomeAnimationFactory homeAnimationFactory) { 186 final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); 187 final FloatingIconView fiv = homeAnimationFactory.mIconView; 188 final boolean isFloatingIconView = fiv != null; 189 190 mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor); 191 mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress)); 192 RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); 193 194 // Matrix to map a rect in Launcher space to window space 195 Matrix homeToWindowPositionMap = new Matrix(); 196 mTaskViewSimulator.applyWindowToHomeRotation(homeToWindowPositionMap); 197 198 final RectF startRect = new RectF(cropRectF); 199 mTaskViewSimulator.getCurrentMatrix().mapRect(startRect); 200 // Move the startRect to Launcher space as floatingIconView runs in Launcher 201 Matrix windowToHomePositionMap = new Matrix(); 202 homeToWindowPositionMap.invert(windowToHomePositionMap); 203 windowToHomePositionMap.mapRect(startRect); 204 205 RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext); 206 if (isFloatingIconView) { 207 anim.addAnimatorListener(fiv); 208 fiv.setOnTargetChangeListener(anim::onTargetPositionChanged); 209 fiv.setFastFinishRunnable(anim::end); 210 } 211 212 SpringAnimationRunner runner = new SpringAnimationRunner( 213 homeAnimationFactory, cropRectF, homeToWindowPositionMap); 214 anim.addOnUpdateListener(runner); 215 anim.addAnimatorListener(runner); 216 return anim; 217 } 218 219 /** 220 * @param progress The progress of the animation to the home screen. 221 * @return The current alpha to set on the animating app window. 222 */ getWindowAlpha(float progress)223 protected float getWindowAlpha(float progress) { 224 // Alpha interpolates between [1, 0] between progress values [start, end] 225 final float start = 0f; 226 final float end = 0.85f; 227 228 if (progress <= start) { 229 return 1f; 230 } 231 if (progress >= end) { 232 return 0f; 233 } 234 return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); 235 } 236 237 protected class SpringAnimationRunner extends AnimationSuccessListener 238 implements RectFSpringAnim.OnUpdateListener, BuilderProxy { 239 240 final Rect mCropRect = new Rect(); 241 final Matrix mMatrix = new Matrix(); 242 243 final RectF mWindowCurrentRect = new RectF(); 244 final Matrix mHomeToWindowPositionMap; 245 246 final FloatingIconView mFIV; 247 final AnimatorPlaybackController mHomeAnim; 248 final RectF mCropRectF; 249 250 final float mStartRadius; 251 final float mEndRadius; 252 final float mWindowAlphaThreshold; 253 SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, Matrix homeToWindowPositionMap)254 SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, 255 Matrix homeToWindowPositionMap) { 256 mHomeAnim = factory.createActivityAnimationToHome(); 257 mCropRectF = cropRectF; 258 mHomeToWindowPositionMap = homeToWindowPositionMap; 259 260 cropRectF.roundOut(mCropRect); 261 mFIV = factory.mIconView; 262 263 // End on a "round-enough" radius so that the shape reveal doesn't have to do too much 264 // rounding at the end of the animation. 265 mStartRadius = mTaskViewSimulator.getCurrentCornerRadius(); 266 mEndRadius = cropRectF.width() / 2f; 267 268 // We want the window alpha to be 0 once this threshold is met, so that the 269 // FolderIconView can be seen morphing into the icon shape. 270 mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f; 271 } 272 273 @Override onUpdate(RectF currentRect, float progress)274 public void onUpdate(RectF currentRect, float progress) { 275 mHomeAnim.setPlayFraction(progress); 276 mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect); 277 278 mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL); 279 float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius); 280 mTransformParams 281 .setTargetAlpha(getWindowAlpha(progress)) 282 .setCornerRadius(cornerRadius); 283 284 mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); 285 if (mFIV != null) { 286 mFIV.update(currentRect, 1f, progress, 287 mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false); 288 } 289 } 290 291 @Override onBuildTargetParams( Builder builder, RemoteAnimationTargetCompat app, TransformParams params)292 public void onBuildTargetParams( 293 Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { 294 builder.withMatrix(mMatrix) 295 .withWindowCrop(mCropRect) 296 .withCornerRadius(params.getCornerRadius()); 297 } 298 299 @Override onCancel()300 public void onCancel() { 301 if (mFIV != null) { 302 mFIV.fastFinish(); 303 } 304 } 305 306 @Override onAnimationStart(Animator animation)307 public void onAnimationStart(Animator animation) { 308 mHomeAnim.dispatchOnStart(); 309 } 310 311 @Override onAnimationSuccess(Animator animator)312 public void onAnimationSuccess(Animator animator) { 313 mHomeAnim.getAnimationPlayer().end(); 314 } 315 } 316 317 public interface RunningWindowAnim { end()318 void end(); 319 cancel()320 void cancel(); 321 wrap(Animator animator)322 static RunningWindowAnim wrap(Animator animator) { 323 return new RunningWindowAnim() { 324 @Override 325 public void end() { 326 animator.end(); 327 } 328 329 @Override 330 public void cancel() { 331 animator.cancel(); 332 } 333 }; 334 } 335 wrap(RectFSpringAnim rectFSpringAnim)336 static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) { 337 return new RunningWindowAnim() { 338 @Override 339 public void end() { 340 rectFSpringAnim.end(); 341 } 342 343 @Override 344 public void cancel() { 345 rectFSpringAnim.cancel(); 346 } 347 }; 348 } 349 } 350 } 351