• 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.app.animation.Interpolators.ACCELERATE_1_5;
19 import static com.android.app.animation.Interpolators.LINEAR;
20 
21 import android.animation.Animator;
22 import android.content.Context;
23 import android.graphics.Matrix;
24 import android.graphics.Matrix.ScaleToFit;
25 import android.graphics.Rect;
26 import android.graphics.RectF;
27 import android.view.RemoteAnimationTarget;
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.AnimatedFloat;
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.RemoteTargetGluer.RemoteTargetHandle;
40 import com.android.quickstep.util.AnimatorControllerWithResistance;
41 import com.android.quickstep.util.RectFSpringAnim;
42 import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
43 import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig;
44 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
45 import com.android.quickstep.util.TaskViewSimulator;
46 import com.android.quickstep.util.TransformParams;
47 import com.android.quickstep.util.TransformParams.BuilderProxy;
48 
49 import java.util.Arrays;
50 import java.util.function.Consumer;
51 
52 public abstract class SwipeUpAnimationLogic implements
53         RecentsAnimationCallbacks.RecentsAnimationListener{
54 
55     protected static final Rect TEMP_RECT = new Rect();
56     protected final RemoteTargetGluer mTargetGluer;
57 
58     protected DeviceProfile mDp;
59 
60     protected final Context mContext;
61     protected final RecentsAnimationDeviceState mDeviceState;
62     protected final GestureState mGestureState;
63 
64     protected RemoteTargetHandle[] mRemoteTargetHandles;
65 
66     // Shift in the range of [0, 1].
67     // 0 => preview snapShot is completely visible, and hotseat is completely translated down
68     // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
69     // visible.
70     protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::onCurrentShiftUpdated);
71     protected float mCurrentDisplacement;
72 
73     // The distance needed to drag to reach the task size in recents.
74     protected int mTransitionDragLength;
75     // How much further we can drag past recents, as a factor of mTransitionDragLength.
76     protected float mDragLengthFactor = 1;
77 
78     protected boolean mIsSwipeForSplit;
79 
SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState)80     public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
81             GestureState gestureState) {
82         mContext = context;
83         mDeviceState = deviceState;
84         mGestureState = gestureState;
85         updateIsGestureForSplit(TopTaskTracker.INSTANCE.get(context)
86                 .getRunningSplitTaskIds().length);
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 = overrideDisplacementForTransientTaskbar(-displacement);
124         mCurrentDisplacement = displacement;
125 
126         float shift;
127         if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
128             shift = mDragLengthFactor;
129         } else {
130             float translation = Math.max(displacement, 0);
131             shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
132         }
133 
134         mCurrentShift.updateValue(shift);
135     }
136 
137     /**
138      * When Transient Taskbar is enabled, subclasses can override the displacement to keep the app
139      * window at the bottom of the screen while taskbar is being swiped in.
140      * @param displacement The distance the user has swiped up from the bottom of the screen. This
141      *                     value will be positive unless the user swipe downwards.
142      * @return the overridden displacement.
143      */
overrideDisplacementForTransientTaskbar(float displacement)144     protected float overrideDisplacementForTransientTaskbar(float displacement) {
145         return displacement;
146     }
147 
148     /**
149      * Called when the value of {@link #mCurrentShift} changes
150      */
151     @UiThread
onCurrentShiftUpdated()152     public abstract void onCurrentShiftUpdated();
153 
getOrientationHandler()154     protected PagedOrientationHandler getOrientationHandler() {
155         // OrientationHandler should be independent of remote target, can directly take one
156         return mRemoteTargetHandles[0].getTaskViewSimulator()
157                 .getOrientationState().getOrientationHandler();
158     }
159 
160     protected abstract class HomeAnimationFactory {
161         protected float mSwipeVelocity;
162 
163         /**
164          * Returns true if we know the home animation involves an item in the hotseat.
165          */
isInHotseat()166         public boolean isInHotseat() {
167             return false;
168         }
169 
getWindowTargetRect()170         public @NonNull RectF getWindowTargetRect() {
171             PagedOrientationHandler orientationHandler = getOrientationHandler();
172             DeviceProfile dp = mDp;
173             final int halfIconSize = dp.iconSizePx / 2;
174             float primaryDimension = orientationHandler
175                     .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
176             float secondaryDimension = orientationHandler
177                     .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
178             final float targetX =  primaryDimension / 2f;
179             final float targetY = secondaryDimension - dp.hotseatBarSizePx;
180             // Fallback to animate to center of screen.
181             return new RectF(targetX - halfIconSize, targetY - halfIconSize,
182                     targetX + halfIconSize, targetY + halfIconSize);
183         }
184 
185         /** Returns the corner radius of the window at the end of the animation. */
getEndRadius(RectF cropRectF)186         public float getEndRadius(RectF cropRectF) {
187             return cropRectF.width() / 2f;
188         }
189 
createActivityAnimationToHome()190         public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
191 
setSwipeVelocity(float velocity)192         public void setSwipeVelocity(float velocity) {
193             mSwipeVelocity = velocity;
194         }
195 
playAtomicAnimation(float velocity)196         public void playAtomicAnimation(float velocity) {
197             // No-op
198         }
199 
setAnimation(RectFSpringAnim anim)200         public void setAnimation(RectFSpringAnim anim) { }
201 
update(RectF currentRect, float progress, float radius)202         public void update(RectF currentRect, float progress, float radius) { }
203 
onCancel()204         public void onCancel() { }
205 
206         /**
207          * @param progress The progress of the animation to the home screen.
208          * @return The current alpha to set on the animating app window.
209          */
getWindowAlpha(float progress)210         protected float getWindowAlpha(float progress) {
211             // Alpha interpolates between [1, 0] between progress values [start, end]
212             final float start = 0f;
213             final float end = 0.85f;
214 
215             if (progress <= start) {
216                 return 1f;
217             }
218             if (progress >= end) {
219                 return 0f;
220             }
221             return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
222         }
223     }
224 
225     /**
226      * Update with start progress for window animation to home.
227      * @param outMatrix {@link Matrix} to map a rect in Launcher space to window space.
228      * @param startProgress The progress of {@link #mCurrentShift} to start thw window from.
229      * @return {@link RectF} represents the bounds as starting point in window space.
230      */
updateProgressForStartRect(Matrix[] outMatrix, float startProgress)231     protected RectF[] updateProgressForStartRect(Matrix[] outMatrix, float startProgress) {
232         mCurrentShift.updateValue(startProgress);
233         RectF[] startRects = new RectF[mRemoteTargetHandles.length];
234         for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
235                 i < mRemoteTargetHandlesLength; i++) {
236             RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
237             TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator();
238             tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress));
239 
240             startRects[i] = new RectF(tvs.getCurrentCropRect());
241             outMatrix[i] = new Matrix();
242             tvs.applyWindowToHomeRotation(outMatrix[i]);
243             tvs.getCurrentMatrix().mapRect(startRects[i]);
244         }
245         return startRects;
246     }
247 
248     /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer)249     protected void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
250         for (RemoteTargetHandle handle : mRemoteTargetHandles) {
251             consumer.accept(handle);
252         }
253     }
254 
255     /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */
getRemoteTaskViewSimulators()256     protected TaskViewSimulator[] getRemoteTaskViewSimulators() {
257         return Arrays.stream(mRemoteTargetHandles)
258                 .map(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator())
259                 .toArray(TaskViewSimulator[]::new);
260     }
261 
262     /**
263      * Creates an animation that transforms the current app window into the home app.
264      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
265      * @param homeAnimationFactory The home animation factory.
266      */
createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)267     protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
268             HomeAnimationFactory homeAnimationFactory) {
269         // TODO(b/195473584) compute separate end targets for different staged split
270         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
271         RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length];
272         Matrix[] homeToWindowPositionMap = new Matrix[mRemoteTargetHandles.length];
273         RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
274         for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
275                 i < mRemoteTargetHandlesLength; i++) {
276             RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
277             out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
278                     targetRect, remoteHandle.getTransformParams(),
279                     remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]);
280         }
281         return out;
282     }
283 
updateIsGestureForSplit(int targetCount)284     protected void updateIsGestureForSplit(int targetCount) {
285         mIsSwipeForSplit = targetCount > 1;
286     }
287 
getWindowAnimationToHomeInternal( HomeAnimationFactory homeAnimationFactory, RectF targetRect, TransformParams transformParams, TaskViewSimulator taskViewSimulator, RectF startRect, Matrix homeToWindowPositionMap)288     private RectFSpringAnim getWindowAnimationToHomeInternal(
289             HomeAnimationFactory homeAnimationFactory, RectF targetRect,
290             TransformParams transformParams, TaskViewSimulator taskViewSimulator,
291             RectF startRect, Matrix homeToWindowPositionMap) {
292         RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
293         // Move the startRect to Launcher space as floatingIconView runs in Launcher
294         Matrix windowToHomePositionMap = new Matrix();
295 
296         // If the start rect ends up overshooting too much to the left/right offscreen, bring it
297         // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
298         // the pageScroll value for a given taskView, see b/228829958#comment12
299         mRemoteTargetHandles[0].getTaskViewSimulator().getOrientationState().getOrientationHandler()
300                 .fixBoundsForHomeAnimStartRect(startRect, mDp);
301 
302         homeToWindowPositionMap.invert(windowToHomePositionMap);
303         windowToHomePositionMap.mapRect(startRect);
304 
305         boolean useTaskbarHotseatParams = mDp.isTaskbarPresent
306                 && homeAnimationFactory.isInHotseat();
307         RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams
308                 ? new TaskbarHotseatSpringConfig(mContext, startRect, targetRect)
309                 : new DefaultSpringConfig(mContext, mDp, startRect, targetRect));
310         homeAnimationFactory.setAnimation(anim);
311 
312         SpringAnimationRunner runner = new SpringAnimationRunner(
313                 homeAnimationFactory, cropRectF, homeToWindowPositionMap,
314                 transformParams, taskViewSimulator);
315         anim.addAnimatorListener(runner);
316         anim.addOnUpdateListener(runner);
317         return anim;
318     }
319 
320     protected class SpringAnimationRunner extends AnimationSuccessListener
321             implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
322 
323         final Rect mCropRect = new Rect();
324         final Matrix mMatrix = new Matrix();
325 
326         final RectF mWindowCurrentRect = new RectF();
327         final Matrix mHomeToWindowPositionMap;
328         private final TransformParams mLocalTransformParams;
329         final HomeAnimationFactory mAnimationFactory;
330 
331         final AnimatorPlaybackController mHomeAnim;
332         final RectF mCropRectF;
333 
334         final float mStartRadius;
335         final float mEndRadius;
336 
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, Matrix homeToWindowPositionMap, TransformParams transformParams, TaskViewSimulator taskViewSimulator)337         SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
338                 Matrix homeToWindowPositionMap, TransformParams transformParams,
339                 TaskViewSimulator taskViewSimulator) {
340             mAnimationFactory = factory;
341             mHomeAnim = factory.createActivityAnimationToHome();
342             mCropRectF = cropRectF;
343             mHomeToWindowPositionMap = homeToWindowPositionMap;
344             mLocalTransformParams = transformParams;
345 
346             cropRectF.roundOut(mCropRect);
347 
348             // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
349             // rounding at the end of the animation.
350             mStartRadius = taskViewSimulator.getCurrentCornerRadius();
351             mEndRadius = factory.getEndRadius(cropRectF);
352         }
353 
354         @Override
onUpdate(RectF currentRect, float progress)355         public void onUpdate(RectF currentRect, float progress) {
356             mHomeAnim.setPlayFraction(progress);
357             mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
358 
359             mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
360             float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
361             float alpha = mAnimationFactory.getWindowAlpha(progress);
362             mLocalTransformParams
363                     .setTargetAlpha(alpha)
364                     .setCornerRadius(cornerRadius);
365             mLocalTransformParams.applySurfaceParams(mLocalTransformParams
366                     .createSurfaceParams(this));
367             mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
368         }
369 
370         @Override
onBuildTargetParams(SurfaceProperties builder, RemoteAnimationTarget app, TransformParams params)371         public void onBuildTargetParams(SurfaceProperties builder, RemoteAnimationTarget app,
372                 TransformParams params) {
373             builder.setMatrix(mMatrix)
374                     .setWindowCrop(mCropRect)
375                     .setCornerRadius(params.getCornerRadius());
376         }
377 
378         @Override
onCancel()379         public void onCancel() {
380             mAnimationFactory.onCancel();
381         }
382 
383         @Override
onAnimationStart(Animator animation)384         public void onAnimationStart(Animator animation) {
385             mHomeAnim.dispatchOnStart();
386         }
387 
388         @Override
onAnimationSuccess(Animator animator)389         public void onAnimationSuccess(Animator animator) {
390             mHomeAnim.getAnimationPlayer().end();
391         }
392     }
393 
394     public interface RunningWindowAnim {
end()395         void end();
396 
cancel()397         void cancel();
398 
wrap(Animator animator)399         static RunningWindowAnim wrap(Animator animator) {
400             return new RunningWindowAnim() {
401                 @Override
402                 public void end() {
403                     animator.end();
404                 }
405 
406                 @Override
407                 public void cancel() {
408                     animator.cancel();
409                 }
410             };
411         }
412 
wrap(RectFSpringAnim rectFSpringAnim)413         static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
414             return new RunningWindowAnim() {
415                 @Override
416                 public void end() {
417                     rectFSpringAnim.end();
418                 }
419 
420                 @Override
421                 public void cancel() {
422                     rectFSpringAnim.cancel();
423                 }
424             };
425         }
426     }
427 }
428