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