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.ENABLE_SPLIT_SELECT; 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.UiThread; 31 32 import com.android.launcher3.DeviceProfile; 33 import com.android.launcher3.Utilities; 34 import com.android.launcher3.anim.AnimationSuccessListener; 35 import com.android.launcher3.anim.AnimatorPlaybackController; 36 import com.android.launcher3.anim.PendingAnimation; 37 import com.android.launcher3.touch.PagedOrientationHandler; 38 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; 39 import com.android.quickstep.util.AnimatorControllerWithResistance; 40 import com.android.quickstep.util.LauncherSplitScreenListener; 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 import java.util.Arrays; 49 import java.util.function.Consumer; 50 51 public abstract class SwipeUpAnimationLogic implements 52 RecentsAnimationCallbacks.RecentsAnimationListener{ 53 54 protected static final Rect TEMP_RECT = new Rect(); 55 protected final RemoteTargetGluer mTargetGluer; 56 57 protected DeviceProfile mDp; 58 59 protected final Context mContext; 60 protected final RecentsAnimationDeviceState mDeviceState; 61 protected final GestureState mGestureState; 62 63 protected RemoteTargetHandle[] mRemoteTargetHandles; 64 65 // Shift in the range of [0, 1]. 66 // 0 => preview snapShot is completely visible, and hotseat is completely translated down 67 // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely 68 // visible. 69 protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift); 70 71 // The distance needed to drag to reach the task size in recents. 72 protected int mTransitionDragLength; 73 // How much further we can drag past recents, as a factor of mTransitionDragLength. 74 protected float mDragLengthFactor = 1; 75 76 protected boolean mIsSwipeForStagedSplit; 77 SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState)78 public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, 79 GestureState gestureState) { 80 mContext = context; 81 mDeviceState = deviceState; 82 mGestureState = gestureState; 83 84 mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() && 85 LauncherSplitScreenListener.INSTANCE.getNoCreate() 86 .getRunningSplitTaskIds().length > 1; 87 88 mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface()); 89 mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles(); 90 runActionOnRemoteHandles(remoteTargetHandle -> 91 remoteTargetHandle.getTaskViewSimulator().getOrientationState().update( 92 mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(), 93 mDeviceState.getRotationTouchHelper().getDisplayRotation() 94 )); 95 } 96 initTransitionEndpoints(DeviceProfile dp)97 protected void initTransitionEndpoints(DeviceProfile dp) { 98 mDp = dp; 99 mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength( 100 dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator() 101 .getOrientationState().getOrientationHandler()); 102 mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; 103 104 for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) { 105 PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2); 106 TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator(); 107 taskViewSimulator.setDp(dp); 108 taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR); 109 AnimatorPlaybackController playbackController = 110 pendingAnimation.createPlaybackController(); 111 112 remoteHandle.setPlaybackController(AnimatorControllerWithResistance.createForRecents( 113 playbackController, mContext, taskViewSimulator.getOrientationState(), 114 mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE, 115 taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE 116 )); 117 } 118 } 119 120 @UiThread updateDisplacement(float displacement)121 public void updateDisplacement(float displacement) { 122 // We are moving in the negative x/y direction 123 displacement = -displacement; 124 float shift; 125 if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { 126 shift = mDragLengthFactor; 127 } else { 128 float translation = Math.max(displacement, 0); 129 shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; 130 } 131 132 mCurrentShift.updateValue(shift); 133 } 134 135 /** 136 * Called when the value of {@link #mCurrentShift} changes 137 */ 138 @UiThread updateFinalShift()139 public abstract void updateFinalShift(); 140 getOrientationHandler()141 protected PagedOrientationHandler getOrientationHandler() { 142 // OrientationHandler should be independent of remote target, can directly take one 143 return mRemoteTargetHandles[0].getTaskViewSimulator() 144 .getOrientationState().getOrientationHandler(); 145 } 146 147 protected abstract class HomeAnimationFactory { 148 protected float mSwipeVelocity; 149 getWindowTargetRect()150 public @NonNull RectF getWindowTargetRect() { 151 PagedOrientationHandler orientationHandler = getOrientationHandler(); 152 DeviceProfile dp = mDp; 153 final int halfIconSize = dp.iconSizePx / 2; 154 float primaryDimension = orientationHandler 155 .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); 156 float secondaryDimension = orientationHandler 157 .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); 158 final float targetX = primaryDimension / 2f; 159 final float targetY = secondaryDimension - dp.hotseatBarSizePx; 160 // Fallback to animate to center of screen. 161 return new RectF(targetX - halfIconSize, targetY - halfIconSize, 162 targetX + halfIconSize, targetY + halfIconSize); 163 } 164 165 /** Returns the corner radius of the window at the end of the animation. */ getEndRadius(RectF cropRectF)166 public float getEndRadius(RectF cropRectF) { 167 return cropRectF.width() / 2f; 168 } 169 createActivityAnimationToHome()170 public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome(); 171 setSwipeVelocity(float velocity)172 public void setSwipeVelocity(float velocity) { 173 mSwipeVelocity = velocity; 174 } 175 playAtomicAnimation(float velocity)176 public void playAtomicAnimation(float velocity) { 177 // No-op 178 } 179 shouldPlayAtomicWorkspaceReveal()180 public boolean shouldPlayAtomicWorkspaceReveal() { 181 return true; 182 } 183 setAnimation(RectFSpringAnim anim)184 public void setAnimation(RectFSpringAnim anim) { } 185 update(RectF currentRect, float progress, float radius)186 public void update(RectF currentRect, float progress, float radius) { } 187 onCancel()188 public void onCancel() { } 189 190 /** 191 * @return {@code true} if this factory supports animating an Activity to PiP window on 192 * swiping up to home. 193 */ supportSwipePipToHome()194 public boolean supportSwipePipToHome() { 195 return false; 196 } 197 198 /** 199 * @param progress The progress of the animation to the home screen. 200 * @return The current alpha to set on the animating app window. 201 */ getWindowAlpha(float progress)202 protected float getWindowAlpha(float progress) { 203 // Alpha interpolates between [1, 0] between progress values [start, end] 204 final float start = 0f; 205 final float end = 0.85f; 206 207 if (progress <= start) { 208 return 1f; 209 } 210 if (progress >= end) { 211 return 0f; 212 } 213 return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); 214 } 215 } 216 217 /** 218 * Update with start progress for window animation to home. 219 * @param outMatrix {@link Matrix} to map a rect in Launcher space to window space. 220 * @param startProgress The progress of {@link #mCurrentShift} to start thw window from. 221 * @return {@link RectF} represents the bounds as starting point in window space. 222 */ updateProgressForStartRect(Matrix[] outMatrix, float startProgress)223 protected RectF[] updateProgressForStartRect(Matrix[] outMatrix, float startProgress) { 224 mCurrentShift.updateValue(startProgress); 225 RectF[] startRects = new RectF[mRemoteTargetHandles.length]; 226 for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length; 227 i < mRemoteTargetHandlesLength; i++) { 228 RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i]; 229 TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator(); 230 tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress)); 231 232 startRects[i] = new RectF(tvs.getCurrentCropRect()); 233 outMatrix[i] = new Matrix(); 234 tvs.applyWindowToHomeRotation(outMatrix[i]); 235 tvs.getCurrentMatrix().mapRect(startRects[i]); 236 } 237 return startRects; 238 } 239 240 /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */ runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer)241 protected void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) { 242 for (RemoteTargetHandle handle : mRemoteTargetHandles) { 243 consumer.accept(handle); 244 } 245 } 246 247 /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */ getRemoteTaskViewSimulators()248 protected TaskViewSimulator[] getRemoteTaskViewSimulators() { 249 return Arrays.stream(mRemoteTargetHandles) 250 .map(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()) 251 .toArray(TaskViewSimulator[]::new); 252 } 253 254 /** 255 * Creates an animation that transforms the current app window into the home app. 256 * @param startProgress The progress of {@link #mCurrentShift} to start the window from. 257 * @param homeAnimationFactory The home animation factory. 258 */ createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)259 protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress, 260 HomeAnimationFactory homeAnimationFactory) { 261 // TODO(b/195473584) compute separate end targets for different staged split 262 final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); 263 RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length]; 264 Matrix[] homeToWindowPositionMap = new Matrix[mRemoteTargetHandles.length]; 265 RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress); 266 for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length; 267 i < mRemoteTargetHandlesLength; i++) { 268 RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i]; 269 out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory, 270 targetRect, remoteHandle.getTransformParams(), 271 remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]); 272 } 273 return out; 274 } 275 getWindowAnimationToHomeInternal( HomeAnimationFactory homeAnimationFactory, RectF targetRect, TransformParams transformParams, TaskViewSimulator taskViewSimulator, RectF startRect, Matrix homeToWindowPositionMap)276 private RectFSpringAnim getWindowAnimationToHomeInternal( 277 HomeAnimationFactory homeAnimationFactory, RectF targetRect, 278 TransformParams transformParams, TaskViewSimulator taskViewSimulator, 279 RectF startRect, Matrix homeToWindowPositionMap) { 280 RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect()); 281 // Move the startRect to Launcher space as floatingIconView runs in Launcher 282 Matrix windowToHomePositionMap = new Matrix(); 283 homeToWindowPositionMap.invert(windowToHomePositionMap); 284 windowToHomePositionMap.mapRect(startRect); 285 286 RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext, mDp); 287 homeAnimationFactory.setAnimation(anim); 288 289 SpringAnimationRunner runner = new SpringAnimationRunner( 290 homeAnimationFactory, cropRectF, homeToWindowPositionMap, 291 transformParams, taskViewSimulator); 292 anim.addAnimatorListener(runner); 293 anim.addOnUpdateListener(runner); 294 return anim; 295 } 296 297 protected class SpringAnimationRunner extends AnimationSuccessListener 298 implements RectFSpringAnim.OnUpdateListener, BuilderProxy { 299 300 final Rect mCropRect = new Rect(); 301 final Matrix mMatrix = new Matrix(); 302 303 final RectF mWindowCurrentRect = new RectF(); 304 final Matrix mHomeToWindowPositionMap; 305 private final TransformParams mLocalTransformParams; 306 final HomeAnimationFactory mAnimationFactory; 307 308 final AnimatorPlaybackController mHomeAnim; 309 final RectF mCropRectF; 310 311 final float mStartRadius; 312 final float mEndRadius; 313 SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, Matrix homeToWindowPositionMap, TransformParams transformParams, TaskViewSimulator taskViewSimulator)314 SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, 315 Matrix homeToWindowPositionMap, TransformParams transformParams, 316 TaskViewSimulator taskViewSimulator) { 317 mAnimationFactory = factory; 318 mHomeAnim = factory.createActivityAnimationToHome(); 319 mCropRectF = cropRectF; 320 mHomeToWindowPositionMap = homeToWindowPositionMap; 321 mLocalTransformParams = transformParams; 322 323 cropRectF.roundOut(mCropRect); 324 325 // End on a "round-enough" radius so that the shape reveal doesn't have to do too much 326 // rounding at the end of the animation. 327 mStartRadius = taskViewSimulator.getCurrentCornerRadius(); 328 mEndRadius = factory.getEndRadius(cropRectF); 329 } 330 331 @Override onUpdate(RectF currentRect, float progress)332 public void onUpdate(RectF currentRect, float progress) { 333 mHomeAnim.setPlayFraction(progress); 334 mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect); 335 336 mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL); 337 float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius); 338 float alpha = mAnimationFactory.getWindowAlpha(progress); 339 mLocalTransformParams 340 .setTargetAlpha(alpha) 341 .setCornerRadius(cornerRadius); 342 mLocalTransformParams.applySurfaceParams(mLocalTransformParams 343 .createSurfaceParams(this)); 344 mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius)); 345 } 346 347 @Override onBuildTargetParams( Builder builder, RemoteAnimationTargetCompat app, TransformParams params)348 public void onBuildTargetParams( 349 Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { 350 builder.withMatrix(mMatrix) 351 .withWindowCrop(mCropRect) 352 .withCornerRadius(params.getCornerRadius()); 353 } 354 355 @Override onCancel()356 public void onCancel() { 357 mAnimationFactory.onCancel(); 358 } 359 360 @Override onAnimationStart(Animator animation)361 public void onAnimationStart(Animator animation) { 362 mHomeAnim.dispatchOnStart(); 363 } 364 365 @Override onAnimationSuccess(Animator animator)366 public void onAnimationSuccess(Animator animator) { 367 mHomeAnim.getAnimationPlayer().end(); 368 } 369 } 370 371 public interface RunningWindowAnim { end()372 void end(); 373 cancel()374 void cancel(); 375 wrap(Animator animator)376 static RunningWindowAnim wrap(Animator animator) { 377 return new RunningWindowAnim() { 378 @Override 379 public void end() { 380 animator.end(); 381 } 382 383 @Override 384 public void cancel() { 385 animator.cancel(); 386 } 387 }; 388 } 389 wrap(RectFSpringAnim rectFSpringAnim)390 static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) { 391 return new RunningWindowAnim() { 392 @Override 393 public void end() { 394 rectFSpringAnim.end(); 395 } 396 397 @Override 398 public void cancel() { 399 rectFSpringAnim.cancel(); 400 } 401 }; 402 } 403 } 404 } 405