• 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;
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