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.LINEAR; 20 import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE; 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 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 import androidx.annotation.UiThread; 32 33 import com.android.launcher3.DeviceProfile; 34 import com.android.launcher3.Utilities; 35 import com.android.launcher3.anim.AnimationSuccessListener; 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.util.AnimatorControllerWithResistance; 40 import com.android.quickstep.util.AppCloseConfig; 41 import com.android.quickstep.util.RectFSpringAnim; 42 import com.android.quickstep.util.RectFSpringAnim2; 43 import com.android.quickstep.util.TaskViewSimulator; 44 import com.android.quickstep.util.TransformParams; 45 import com.android.quickstep.util.TransformParams.BuilderProxy; 46 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 47 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; 48 49 public abstract class SwipeUpAnimationLogic { 50 51 protected static final Rect TEMP_RECT = new Rect(); 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 73 protected AnimatorControllerWithResistance mWindowTransitionController; 74 SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState, TransformParams transformParams)75 public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, 76 GestureState gestureState, TransformParams transformParams) { 77 mContext = context; 78 mDeviceState = deviceState; 79 mGestureState = gestureState; 80 mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); 81 mTransformParams = transformParams; 82 83 mTaskViewSimulator.getOrientationState().update( 84 mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(), 85 mDeviceState.getRotationTouchHelper().getDisplayRotation()); 86 } 87 initTransitionEndpoints(DeviceProfile dp)88 protected void initTransitionEndpoints(DeviceProfile dp) { 89 mDp = dp; 90 91 mTaskViewSimulator.setDp(dp); 92 mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength( 93 dp, mContext, TEMP_RECT, 94 mTaskViewSimulator.getOrientationState().getOrientationHandler()); 95 mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; 96 97 PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); 98 mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR); 99 AnimatorPlaybackController normalController = pa.createPlaybackController(); 100 mWindowTransitionController = AnimatorControllerWithResistance.createForRecents( 101 normalController, mContext, mTaskViewSimulator.getOrientationState(), 102 mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE, 103 mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE); 104 } 105 106 @UiThread updateDisplacement(float displacement)107 public void updateDisplacement(float displacement) { 108 // We are moving in the negative x/y direction 109 displacement = -displacement; 110 float shift; 111 if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { 112 shift = mDragLengthFactor; 113 } else { 114 float translation = Math.max(displacement, 0); 115 shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; 116 } 117 118 mCurrentShift.updateValue(shift); 119 } 120 121 /** 122 * Called when the value of {@link #mCurrentShift} changes 123 */ 124 @UiThread updateFinalShift()125 public abstract void updateFinalShift(); 126 getOrientationHandler()127 protected PagedOrientationHandler getOrientationHandler() { 128 return mTaskViewSimulator.getOrientationState().getOrientationHandler(); 129 } 130 131 protected abstract class HomeAnimationFactory { 132 protected float mSwipeVelocity; 133 getWindowTargetRect()134 public @NonNull RectF getWindowTargetRect() { 135 PagedOrientationHandler orientationHandler = getOrientationHandler(); 136 DeviceProfile dp = mDp; 137 final int halfIconSize = dp.iconSizePx / 2; 138 float primaryDimension = orientationHandler 139 .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); 140 float secondaryDimension = orientationHandler 141 .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); 142 final float targetX = primaryDimension / 2f; 143 final float targetY = secondaryDimension - dp.hotseatBarSizePx; 144 // Fallback to animate to center of screen. 145 return new RectF(targetX - halfIconSize, targetY - halfIconSize, 146 targetX + halfIconSize, targetY + halfIconSize); 147 } 148 149 /** Returns the corner radius of the window at the end of the animation. */ getEndRadius(RectF cropRectF)150 public float getEndRadius(RectF cropRectF) { 151 return cropRectF.width() / 2f; 152 } 153 createActivityAnimationToHome()154 public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome(); 155 setSwipeVelocity(float velocity)156 public void setSwipeVelocity(float velocity) { 157 mSwipeVelocity = velocity; 158 } 159 playAtomicAnimation(float velocity)160 public void playAtomicAnimation(float velocity) { 161 // No-op 162 } 163 shouldPlayAtomicWorkspaceReveal()164 public boolean shouldPlayAtomicWorkspaceReveal() { 165 return true; 166 } 167 setAnimation(RectFSpringAnim anim)168 public void setAnimation(RectFSpringAnim anim) { } 169 keepWindowOpaque()170 public boolean keepWindowOpaque() { return false; } 171 update(@ullable AppCloseConfig config, RectF currentRect, float progress, float radius)172 public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress, 173 float radius) { } 174 onCancel()175 public void onCancel() { } 176 177 /** 178 * @return {@code true} if this factory supports animating an Activity to PiP window on 179 * swiping up to home. 180 */ supportSwipePipToHome()181 public boolean supportSwipePipToHome() { 182 return false; 183 } 184 185 /** 186 * @param progress The progress of the animation to the home screen. 187 * @return The current alpha to set on the animating app window. 188 */ getWindowAlpha(float progress)189 protected float getWindowAlpha(float progress) { 190 // Alpha interpolates between [1, 0] between progress values [start, end] 191 final float start = 0f; 192 final float end = 0.85f; 193 194 if (progress <= start) { 195 return 1f; 196 } 197 if (progress >= end) { 198 return 0f; 199 } 200 return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); 201 } 202 } 203 204 /** 205 * Update with start progress for window animation to home. 206 * @param outMatrix {@link Matrix} to map a rect in Launcher space to window space. 207 * @param startProgress The progress of {@link #mCurrentShift} to start thw window from. 208 * @return {@link RectF} represents the bounds as starting point in window space. 209 */ updateProgressForStartRect(Matrix outMatrix, float startProgress)210 protected RectF updateProgressForStartRect(Matrix outMatrix, float startProgress) { 211 mCurrentShift.updateValue(startProgress); 212 mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress)); 213 RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); 214 215 mTaskViewSimulator.applyWindowToHomeRotation(outMatrix); 216 217 final RectF startRect = new RectF(cropRectF); 218 mTaskViewSimulator.getCurrentMatrix().mapRect(startRect); 219 return startRect; 220 } 221 222 /** 223 * Creates an animation that transforms the current app window into the home app. 224 * @param startProgress The progress of {@link #mCurrentShift} to start the window from. 225 * @param homeAnimationFactory The home animation factory. 226 */ createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)227 protected RectFSpringAnim createWindowAnimationToHome(float startProgress, 228 HomeAnimationFactory homeAnimationFactory) { 229 final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); 230 231 Matrix homeToWindowPositionMap = new Matrix(); 232 final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress); 233 RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect()); 234 235 // Move the startRect to Launcher space as floatingIconView runs in Launcher 236 Matrix windowToHomePositionMap = new Matrix(); 237 homeToWindowPositionMap.invert(windowToHomePositionMap); 238 windowToHomePositionMap.mapRect(startRect); 239 240 RectFSpringAnim anim; 241 if (PROTOTYPE_APP_CLOSE.get()) { 242 anim = new RectFSpringAnim2(startRect, targetRect, mContext, 243 mTaskViewSimulator.getCurrentCornerRadius(), 244 homeAnimationFactory.getEndRadius(cropRectF)); 245 } else { 246 anim = new RectFSpringAnim(startRect, targetRect, mContext); 247 } 248 homeAnimationFactory.setAnimation(anim); 249 250 SpringAnimationRunner runner = new SpringAnimationRunner( 251 homeAnimationFactory, cropRectF, homeToWindowPositionMap); 252 anim.addOnUpdateListener(runner); 253 anim.addAnimatorListener(runner); 254 return anim; 255 } 256 257 protected class SpringAnimationRunner extends AnimationSuccessListener 258 implements RectFSpringAnim.OnUpdateListener, BuilderProxy { 259 260 final Rect mCropRect = new Rect(); 261 final Matrix mMatrix = new Matrix(); 262 263 final RectF mWindowCurrentRect = new RectF(); 264 final Matrix mHomeToWindowPositionMap; 265 final HomeAnimationFactory mAnimationFactory; 266 267 final AnimatorPlaybackController mHomeAnim; 268 final RectF mCropRectF; 269 270 final float mStartRadius; 271 final float mEndRadius; 272 SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, Matrix homeToWindowPositionMap)273 SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, 274 Matrix homeToWindowPositionMap) { 275 mAnimationFactory = factory; 276 mHomeAnim = factory.createActivityAnimationToHome(); 277 mCropRectF = cropRectF; 278 mHomeToWindowPositionMap = homeToWindowPositionMap; 279 280 cropRectF.roundOut(mCropRect); 281 282 // End on a "round-enough" radius so that the shape reveal doesn't have to do too much 283 // rounding at the end of the animation. 284 mStartRadius = mTaskViewSimulator.getCurrentCornerRadius(); 285 mEndRadius = factory.getEndRadius(cropRectF); 286 } 287 288 @Override onUpdate(@ullable AppCloseConfig config, RectF currentRect, float progress)289 public void onUpdate(@Nullable AppCloseConfig config, RectF currentRect, float progress) { 290 mHomeAnim.setPlayFraction(progress); 291 mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect); 292 293 mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL); 294 float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius); 295 float alpha = mAnimationFactory.getWindowAlpha(progress); 296 if (config != null && PROTOTYPE_APP_CLOSE.get()) { 297 alpha = config.getWindowAlpha(); 298 cornerRadius = config.getCornerRadius(); 299 } 300 if (mAnimationFactory.keepWindowOpaque()) { 301 alpha = 1f; 302 } 303 mTransformParams 304 .setTargetAlpha(alpha) 305 .setCornerRadius(cornerRadius); 306 mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); 307 mAnimationFactory.update(config, currentRect, progress, 308 mMatrix.mapRadius(cornerRadius)); 309 } 310 311 @Override onBuildTargetParams( Builder builder, RemoteAnimationTargetCompat app, TransformParams params)312 public void onBuildTargetParams( 313 Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { 314 builder.withMatrix(mMatrix) 315 .withWindowCrop(mCropRect) 316 .withCornerRadius(params.getCornerRadius()); 317 } 318 319 @Override onCancel()320 public void onCancel() { 321 mAnimationFactory.onCancel(); 322 } 323 324 @Override onAnimationStart(Animator animation)325 public void onAnimationStart(Animator animation) { 326 mHomeAnim.dispatchOnStart(); 327 } 328 329 @Override onAnimationSuccess(Animator animator)330 public void onAnimationSuccess(Animator animator) { 331 mHomeAnim.getAnimationPlayer().end(); 332 } 333 } 334 335 public interface RunningWindowAnim { end()336 void end(); 337 cancel()338 void cancel(); 339 wrap(Animator animator)340 static RunningWindowAnim wrap(Animator animator) { 341 return new RunningWindowAnim() { 342 @Override 343 public void end() { 344 animator.end(); 345 } 346 347 @Override 348 public void cancel() { 349 animator.cancel(); 350 } 351 }; 352 } 353 wrap(RectFSpringAnim rectFSpringAnim)354 static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) { 355 return new RunningWindowAnim() { 356 @Override 357 public void end() { 358 rectFSpringAnim.end(); 359 } 360 361 @Override 362 public void cancel() { 363 rectFSpringAnim.cancel(); 364 } 365 }; 366 } 367 } 368 } 369