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