• 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 
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::updateFinalShift);
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         mIsSwipeForSplit = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1;
86 
87         mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
88         mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
89         runActionOnRemoteHandles(remoteTargetHandle ->
90                 remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
91                         mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
92                         mDeviceState.getRotationTouchHelper().getDisplayRotation()
93                 ));
94     }
95 
initTransitionEndpoints(DeviceProfile dp)96     protected void initTransitionEndpoints(DeviceProfile dp) {
97         mDp = dp;
98         mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
99                 dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator()
100                         .getOrientationState().getOrientationHandler());
101         mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
102 
103         for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
104             PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
105             TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
106             taskViewSimulator.setDp(dp);
107             taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR);
108             AnimatorPlaybackController playbackController =
109                     pendingAnimation.createPlaybackController();
110 
111             remoteHandle.setPlaybackController(AnimatorControllerWithResistance.createForRecents(
112                     playbackController, mContext, taskViewSimulator.getOrientationState(),
113                     mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
114                     taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE
115             ));
116         }
117     }
118 
119     @UiThread
updateDisplacement(float displacement)120     public void updateDisplacement(float displacement) {
121         // We are moving in the negative x/y direction
122         displacement = overrideDisplacementForTransientTaskbar(-displacement);
123         mCurrentDisplacement = displacement;
124 
125         float shift;
126         if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
127             shift = mDragLengthFactor;
128         } else {
129             float translation = Math.max(displacement, 0);
130             shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
131         }
132 
133         mCurrentShift.updateValue(shift);
134     }
135 
136     /**
137      * When Transient Taskbar is enabled, subclasses can override the displacement to keep the app
138      * window at the bottom of the screen while taskbar is being swiped in.
139      * @param displacement The distance the user has swiped up from the bottom of the screen. This
140      *                     value will be positive unless the user swipe downwards.
141      * @return the overridden displacement.
142      */
overrideDisplacementForTransientTaskbar(float displacement)143     protected float overrideDisplacementForTransientTaskbar(float displacement) {
144         return displacement;
145     }
146 
147     /**
148      * Called when the value of {@link #mCurrentShift} changes
149      */
150     @UiThread
updateFinalShift()151     public abstract void updateFinalShift();
152 
getOrientationHandler()153     protected PagedOrientationHandler getOrientationHandler() {
154         // OrientationHandler should be independent of remote target, can directly take one
155         return mRemoteTargetHandles[0].getTaskViewSimulator()
156                 .getOrientationState().getOrientationHandler();
157     }
158 
159     protected abstract class HomeAnimationFactory {
160         protected float mSwipeVelocity;
161 
162         /**
163          * Returns true if we know the home animation involves an item in the hotseat.
164          */
isInHotseat()165         public boolean isInHotseat() {
166             return false;
167         }
168 
getWindowTargetRect()169         public @NonNull RectF getWindowTargetRect() {
170             PagedOrientationHandler orientationHandler = getOrientationHandler();
171             DeviceProfile dp = mDp;
172             final int halfIconSize = dp.iconSizePx / 2;
173             float primaryDimension = orientationHandler
174                     .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
175             float secondaryDimension = orientationHandler
176                     .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
177             final float targetX =  primaryDimension / 2f;
178             final float targetY = secondaryDimension - dp.hotseatBarSizePx;
179             // Fallback to animate to center of screen.
180             return new RectF(targetX - halfIconSize, targetY - halfIconSize,
181                     targetX + halfIconSize, targetY + halfIconSize);
182         }
183 
184         /** Returns the corner radius of the window at the end of the animation. */
getEndRadius(RectF cropRectF)185         public float getEndRadius(RectF cropRectF) {
186             return cropRectF.width() / 2f;
187         }
188 
createActivityAnimationToHome()189         public abstract @NonNull AnimatorPlaybackController createActivityAnimationToHome();
190 
setSwipeVelocity(float velocity)191         public void setSwipeVelocity(float velocity) {
192             mSwipeVelocity = velocity;
193         }
194 
playAtomicAnimation(float velocity)195         public void playAtomicAnimation(float velocity) {
196             // No-op
197         }
198 
setAnimation(RectFSpringAnim anim)199         public void setAnimation(RectFSpringAnim anim) { }
200 
update(RectF currentRect, float progress, float radius)201         public void update(RectF currentRect, float progress, float radius) { }
202 
onCancel()203         public void onCancel() { }
204 
205         /**
206          * @param progress The progress of the animation to the home screen.
207          * @return The current alpha to set on the animating app window.
208          */
getWindowAlpha(float progress)209         protected float getWindowAlpha(float progress) {
210             // Alpha interpolates between [1, 0] between progress values [start, end]
211             final float start = 0f;
212             final float end = 0.85f;
213 
214             if (progress <= start) {
215                 return 1f;
216             }
217             if (progress >= end) {
218                 return 0f;
219             }
220             return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
221         }
222     }
223 
224     /**
225      * Update with start progress for window animation to home.
226      * @param outMatrix {@link Matrix} to map a rect in Launcher space to window space.
227      * @param startProgress The progress of {@link #mCurrentShift} to start thw window from.
228      * @return {@link RectF} represents the bounds as starting point in window space.
229      */
updateProgressForStartRect(Matrix[] outMatrix, float startProgress)230     protected RectF[] updateProgressForStartRect(Matrix[] outMatrix, float startProgress) {
231         mCurrentShift.updateValue(startProgress);
232         RectF[] startRects = new RectF[mRemoteTargetHandles.length];
233         for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
234                 i < mRemoteTargetHandlesLength; i++) {
235             RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
236             TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator();
237             tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress));
238 
239             startRects[i] = new RectF(tvs.getCurrentCropRect());
240             outMatrix[i] = new Matrix();
241             tvs.applyWindowToHomeRotation(outMatrix[i]);
242             tvs.getCurrentMatrix().mapRect(startRects[i]);
243         }
244         return startRects;
245     }
246 
247     /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer)248     protected void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
249         for (RemoteTargetHandle handle : mRemoteTargetHandles) {
250             consumer.accept(handle);
251         }
252     }
253 
254     /** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */
getRemoteTaskViewSimulators()255     protected TaskViewSimulator[] getRemoteTaskViewSimulators() {
256         return Arrays.stream(mRemoteTargetHandles)
257                 .map(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator())
258                 .toArray(TaskViewSimulator[]::new);
259     }
260 
261     /**
262      * Creates an animation that transforms the current app window into the home app.
263      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
264      * @param homeAnimationFactory The home animation factory.
265      */
createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory)266     protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
267             HomeAnimationFactory homeAnimationFactory) {
268         // TODO(b/195473584) compute separate end targets for different staged split
269         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
270         RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length];
271         Matrix[] homeToWindowPositionMap = new Matrix[mRemoteTargetHandles.length];
272         RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
273         for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
274                 i < mRemoteTargetHandlesLength; i++) {
275             RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
276             out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
277                     targetRect, remoteHandle.getTransformParams(),
278                     remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]);
279         }
280         return out;
281     }
282 
getWindowAnimationToHomeInternal( HomeAnimationFactory homeAnimationFactory, RectF targetRect, TransformParams transformParams, TaskViewSimulator taskViewSimulator, RectF startRect, Matrix homeToWindowPositionMap)283     private RectFSpringAnim getWindowAnimationToHomeInternal(
284             HomeAnimationFactory homeAnimationFactory, RectF targetRect,
285             TransformParams transformParams, TaskViewSimulator taskViewSimulator,
286             RectF startRect, Matrix homeToWindowPositionMap) {
287         RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
288         // Move the startRect to Launcher space as floatingIconView runs in Launcher
289         Matrix windowToHomePositionMap = new Matrix();
290 
291         // If the start rect ends up overshooting too much to the left/right offscreen, bring it
292         // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
293         // the pageScroll value for a given taskView, see b/228829958#comment12
294         mRemoteTargetHandles[0].getTaskViewSimulator().getOrientationState().getOrientationHandler()
295                 .fixBoundsForHomeAnimStartRect(startRect, mDp);
296 
297         homeToWindowPositionMap.invert(windowToHomePositionMap);
298         windowToHomePositionMap.mapRect(startRect);
299 
300         boolean useTaskbarHotseatParams = mDp.isTaskbarPresent
301                 && homeAnimationFactory.isInHotseat();
302         RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams
303                 ? new TaskbarHotseatSpringConfig(mContext, startRect, targetRect)
304                 : new DefaultSpringConfig(mContext, mDp, startRect, targetRect));
305         homeAnimationFactory.setAnimation(anim);
306 
307         SpringAnimationRunner runner = new SpringAnimationRunner(
308                 homeAnimationFactory, cropRectF, homeToWindowPositionMap,
309                 transformParams, taskViewSimulator);
310         anim.addAnimatorListener(runner);
311         anim.addOnUpdateListener(runner);
312         return anim;
313     }
314 
315     protected class SpringAnimationRunner extends AnimationSuccessListener
316             implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
317 
318         final Rect mCropRect = new Rect();
319         final Matrix mMatrix = new Matrix();
320 
321         final RectF mWindowCurrentRect = new RectF();
322         final Matrix mHomeToWindowPositionMap;
323         private final TransformParams mLocalTransformParams;
324         final HomeAnimationFactory mAnimationFactory;
325 
326         final AnimatorPlaybackController mHomeAnim;
327         final RectF mCropRectF;
328 
329         final float mStartRadius;
330         final float mEndRadius;
331 
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, Matrix homeToWindowPositionMap, TransformParams transformParams, TaskViewSimulator taskViewSimulator)332         SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
333                 Matrix homeToWindowPositionMap, TransformParams transformParams,
334                 TaskViewSimulator taskViewSimulator) {
335             mAnimationFactory = factory;
336             mHomeAnim = factory.createActivityAnimationToHome();
337             mCropRectF = cropRectF;
338             mHomeToWindowPositionMap = homeToWindowPositionMap;
339             mLocalTransformParams = transformParams;
340 
341             cropRectF.roundOut(mCropRect);
342 
343             // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
344             // rounding at the end of the animation.
345             mStartRadius = taskViewSimulator.getCurrentCornerRadius();
346             mEndRadius = factory.getEndRadius(cropRectF);
347         }
348 
349         @Override
onUpdate(RectF currentRect, float progress)350         public void onUpdate(RectF currentRect, float progress) {
351             mHomeAnim.setPlayFraction(progress);
352             mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
353 
354             mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
355             float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
356             float alpha = mAnimationFactory.getWindowAlpha(progress);
357             mLocalTransformParams
358                     .setTargetAlpha(alpha)
359                     .setCornerRadius(cornerRadius);
360             mLocalTransformParams.applySurfaceParams(mLocalTransformParams
361                     .createSurfaceParams(this));
362             mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
363         }
364 
365         @Override
onBuildTargetParams(SurfaceProperties builder, RemoteAnimationTarget app, TransformParams params)366         public void onBuildTargetParams(SurfaceProperties builder, RemoteAnimationTarget app,
367                 TransformParams params) {
368             builder.setMatrix(mMatrix)
369                     .setWindowCrop(mCropRect)
370                     .setCornerRadius(params.getCornerRadius());
371         }
372 
373         @Override
onCancel()374         public void onCancel() {
375             mAnimationFactory.onCancel();
376         }
377 
378         @Override
onAnimationStart(Animator animation)379         public void onAnimationStart(Animator animation) {
380             mHomeAnim.dispatchOnStart();
381         }
382 
383         @Override
onAnimationSuccess(Animator animator)384         public void onAnimationSuccess(Animator animator) {
385             mHomeAnim.getAnimationPlayer().end();
386         }
387     }
388 
389     public interface RunningWindowAnim {
end()390         void end();
391 
cancel()392         void cancel();
393 
wrap(Animator animator)394         static RunningWindowAnim wrap(Animator animator) {
395             return new RunningWindowAnim() {
396                 @Override
397                 public void end() {
398                     animator.end();
399                 }
400 
401                 @Override
402                 public void cancel() {
403                     animator.cancel();
404                 }
405             };
406         }
407 
wrap(RectFSpringAnim rectFSpringAnim)408         static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
409             return new RunningWindowAnim() {
410                 @Override
411                 public void end() {
412                     rectFSpringAnim.end();
413                 }
414 
415                 @Override
416                 public void cancel() {
417                     rectFSpringAnim.cancel();
418                 }
419             };
420         }
421     }
422 }
423