• 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.interaction;
17 
18 import static com.android.launcher3.anim.Interpolators.ACCEL;
19 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
20 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
21 import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
22 import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
23 import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
24 
25 import android.animation.Animator;
26 import android.animation.AnimatorListenerAdapter;
27 import android.animation.AnimatorSet;
28 import android.animation.ValueAnimator;
29 import android.annotation.TargetApi;
30 import android.content.Context;
31 import android.graphics.Outline;
32 import android.graphics.PointF;
33 import android.graphics.Rect;
34 import android.graphics.RectF;
35 import android.os.Build;
36 import android.view.View;
37 import android.view.ViewOutlineProvider;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 
42 import com.android.launcher3.DeviceProfile;
43 import com.android.launcher3.InvariantDeviceProfile;
44 import com.android.launcher3.Utilities;
45 import com.android.launcher3.anim.AnimatedFloat;
46 import com.android.launcher3.anim.AnimatorListeners;
47 import com.android.launcher3.anim.AnimatorPlaybackController;
48 import com.android.launcher3.anim.PendingAnimation;
49 import com.android.quickstep.GestureState;
50 import com.android.quickstep.OverviewComponentObserver;
51 import com.android.quickstep.RecentsAnimationDeviceState;
52 import com.android.quickstep.RemoteTargetGluer;
53 import com.android.quickstep.SwipeUpAnimationLogic;
54 import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
55 import com.android.quickstep.util.RecordingSurfaceTransaction;
56 import com.android.quickstep.util.RectFSpringAnim;
57 import com.android.quickstep.util.SurfaceTransaction;
58 import com.android.quickstep.util.SurfaceTransaction.MockProperties;
59 import com.android.quickstep.util.TransformParams;
60 
61 @TargetApi(Build.VERSION_CODES.R)
62 abstract class SwipeUpGestureTutorialController extends TutorialController {
63 
64     private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12);
65 
66     protected static final long TASK_VIEW_END_ANIMATION_DURATION_MILLIS = 300;
67     private static final long HOME_SWIPE_ANIMATION_DURATION_MILLIS = 625;
68     private static final long OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS = 1000;
69 
70     final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation;
71     private float mFakeTaskViewRadius;
72     private final Rect mFakeTaskViewRect = new Rect();
73     RunningWindowAnim mRunningWindowAnim;
74     private boolean mShowTasks = false;
75     private boolean mShowPreviousTasks = false;
76 
77     private final AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() {
78         @Override
79         public void onAnimationEnd(Animator animation) {
80             mFakeHotseatView.setVisibility(View.INVISIBLE);
81             mFakeIconView.setVisibility(View.INVISIBLE);
82             if (mTutorialFragment.getActivity() != null) {
83                 int height = mTutorialFragment.getRootView().getFullscreenHeight();
84                 int width = mTutorialFragment.getRootView().getWidth();
85                 mFakeTaskViewRect.set(0, 0, width, height);
86             }
87             mFakeTaskViewRadius = 0;
88             mFakeTaskView.invalidateOutline();
89             mFakeTaskView.setVisibility(View.VISIBLE);
90             mFakeTaskView.setAlpha(1);
91             mFakePreviousTaskView.setVisibility(View.INVISIBLE);
92             mFakePreviousTaskView.setAlpha(1);
93             mFakePreviousTaskView.setToSingleRowLayout(false);
94             mShowTasks = false;
95             mShowPreviousTasks = false;
96             mRunningWindowAnim = null;
97         }
98     };
99 
SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType)100     SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
101         super(tutorialFragment, tutorialType);
102         RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
103         OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
104         mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
105                 new GestureState(observer, -1));
106         observer.onDestroy();
107         deviceState.destroy();
108 
109         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
110                 .getDeviceProfile(mContext)
111                 .copy(mContext);
112         mTaskViewSwipeUpAnimation.initDp(dp);
113 
114         int height = mTutorialFragment.getRootView().getFullscreenHeight();
115         int width = mTutorialFragment.getRootView().getWidth();
116         mFakeTaskViewRect.set(0, 0, width, height);
117         mFakeTaskViewRadius = 0;
118 
119         ViewOutlineProvider outlineProvider = new ViewOutlineProvider() {
120             @Override
121             public void getOutline(View view, Outline outline) {
122                 outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
123             }
124         };
125 
126         mFakeTaskView.setClipToOutline(true);
127         mFakeTaskView.setOutlineProvider(outlineProvider);
128 
129         mFakePreviousTaskView.setClipToOutline(true);
130         mFakePreviousTaskView.setOutlineProvider(outlineProvider);
131     }
132 
cancelRunningAnimation()133     private void cancelRunningAnimation() {
134         if (mRunningWindowAnim != null) {
135             mRunningWindowAnim.cancel();
136         }
137         mRunningWindowAnim = null;
138     }
139 
140     /** Fades the task view, optionally after animating to a fake Overview. */
fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset, @Nullable Runnable onEndRunnable)141     void fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset,
142                              @Nullable Runnable onEndRunnable) {
143         cancelRunningAnimation();
144         PendingAnimation anim = new PendingAnimation(300);
145         if (toOverviewFirst) {
146             anim.setFloat(mTaskViewSwipeUpAnimation
147                     .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
148             anim.addListener(new AnimatorListenerAdapter() {
149                 @Override
150                 public void onAnimationEnd(Animator animation, boolean isReverse) {
151                     PendingAnimation fadeAnim =
152                             new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS);
153                     if (reset) {
154                         fadeAnim.setFloat(mTaskViewSwipeUpAnimation
155                                 .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
156                         fadeAnim.addListener(mResetTaskView);
157                     } else {
158                         fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
159                         fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
160                     }
161                     if (onEndRunnable != null) {
162                         fadeAnim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable));
163                     }
164                     AnimatorSet animset = fadeAnim.buildAnim();
165 
166                     if (reset && mTutorialFragment.isLargeScreen()) {
167                         animset.addListener(new AnimatorListenerAdapter() {
168                             @Override
169                             public void onAnimationStart(Animator animation) {
170                                 super.onAnimationStart(animation);
171                                 Animator multiRowAnimation =
172                                         mFakePreviousTaskView.createAnimationToMultiRowLayout();
173 
174                                 if (multiRowAnimation != null) {
175                                     multiRowAnimation.setDuration(
176                                             TASK_VIEW_END_ANIMATION_DURATION_MILLIS).start();
177                                 }
178                             }
179                         });
180                     }
181 
182                     animset.setStartDelay(100);
183                     animset.start();
184                     mRunningWindowAnim = RunningWindowAnim.wrap(animset);
185                 }
186             });
187         } else {
188             if (reset) {
189                 anim.setFloat(mTaskViewSwipeUpAnimation
190                         .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
191                 anim.addListener(mResetTaskView);
192             } else {
193                 anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
194                 anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
195             }
196             if (onEndRunnable != null) {
197                 anim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable));
198             }
199         }
200         AnimatorSet animset = anim.buildAnim();
201         hideFakeTaskbar(/* animateToHotseat= */ false);
202         animset.start();
203         mRunningWindowAnim = RunningWindowAnim.wrap(animset);
204     }
205 
resetFakeTaskViewFromOverview()206     void resetFakeTaskViewFromOverview() {
207         resetFakeTaskView(false, false);
208     }
209 
resetFakeTaskView(boolean animateFromHome)210     void resetFakeTaskView(boolean animateFromHome) {
211         resetFakeTaskView(animateFromHome, true);
212     }
213 
resetFakeTaskView(boolean animateFromHome, boolean animateTaskbar)214     void resetFakeTaskView(boolean animateFromHome, boolean animateTaskbar) {
215         mFakeTaskView.setVisibility(View.VISIBLE);
216         PendingAnimation anim = new PendingAnimation(300);
217         anim.setFloat(mTaskViewSwipeUpAnimation
218                 .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL);
219         anim.setViewAlpha(mFakeTaskView, 1, ACCEL);
220         anim.addListener(mResetTaskView);
221         AnimatorSet animset = anim.buildAnim();
222         if (animateTaskbar) {
223             showFakeTaskbar(animateFromHome);
224         }
225         animset.start();
226         mRunningWindowAnim = RunningWindowAnim.wrap(animset);
227     }
228 
animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable)229     void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) {
230         cancelRunningAnimation();
231         hideFakeTaskbar(/* animateToHotseat= */ true);
232         mFakePreviousTaskView.setVisibility(View.INVISIBLE);
233         mFakeHotseatView.setVisibility(View.VISIBLE);
234         mShowPreviousTasks = false;
235         RectFSpringAnim rectAnim =
236                 mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
237         // After home animation finishes, fade out and run onEndRunnable.
238         PendingAnimation fadeAnim = new PendingAnimation(300);
239         fadeAnim.setViewAlpha(mFakeIconView, 0, ACCEL);
240         if (onEndRunnable != null) {
241             fadeAnim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable));
242         }
243         AnimatorSet animset = fadeAnim.buildAnim();
244         rectAnim.addAnimatorListener(AnimatorListeners.forSuccessCallback(animset::start));
245         mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
246     }
247 
248     @Override
setNavBarGestureProgress(@ullable Float displacement)249     public void setNavBarGestureProgress(@Nullable Float displacement) {
250         if (isGestureCompleted()) {
251             return;
252         }
253         if (mTutorialType == HOME_NAVIGATION_COMPLETE
254                 || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
255             mFakeTaskView.setVisibility(View.INVISIBLE);
256             mFakePreviousTaskView.setVisibility(View.INVISIBLE);
257         } else {
258             mShowTasks = true;
259             mFakeTaskView.setVisibility(View.VISIBLE);
260             if (mShowPreviousTasks) {
261                 mFakePreviousTaskView.setVisibility(View.VISIBLE);
262             }
263             if (mRunningWindowAnim == null && displacement != null) {
264                 mTaskViewSwipeUpAnimation.updateDisplacement(displacement);
265             }
266         }
267     }
268 
269     @Override
onMotionPaused(boolean unused)270     public void onMotionPaused(boolean unused) {
271         if (isGestureCompleted()) {
272             return;
273         }
274         if (mShowTasks) {
275             if (!mShowPreviousTasks) {
276                 mFakePreviousTaskView.setTranslationX(
277                         -(2 * mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN));
278                 mFakePreviousTaskView.animate()
279                     .setDuration(300)
280                     .translationX(-(mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN))
281                     .start();
282             }
283             mShowPreviousTasks = true;
284         }
285     }
286 
287     class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
288 
ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState)289         ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
290                              GestureState gestureState) {
291             super(context, deviceState, gestureState);
292             mRemoteTargetHandles[0] = new RemoteTargetGluer.RemoteTargetHandle(
293                     mRemoteTargetHandles[0].getTaskViewSimulator(), new FakeTransformParams());
294 
295             for (RemoteTargetGluer.RemoteTargetHandle handle
296                     : mTargetGluer.getRemoteTargetHandles()) {
297                 // Override home screen rotation preference so that home and overview animations
298                 // work properly
299                 handle.getTaskViewSimulator()
300                         .getOrientationState()
301                         .ignoreAllowHomeRotationPreference();
302             }
303         }
304 
initDp(DeviceProfile dp)305         void initDp(DeviceProfile dp) {
306             initTransitionEndpoints(dp);
307             mRemoteTargetHandles[0].getTaskViewSimulator().setPreviewBounds(
308                     new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
309         }
310 
311         @Override
updateFinalShift()312         public void updateFinalShift() {
313             mRemoteTargetHandles[0].getPlaybackController()
314                     .setProgress(mCurrentShift.value, mDragLengthFactor);
315             mRemoteTargetHandles[0].getTaskViewSimulator().apply(
316                     mRemoteTargetHandles[0].getTransformParams());
317         }
318 
getCurrentShift()319         AnimatedFloat getCurrentShift() {
320             return mCurrentShift;
321         }
322 
handleSwipeUpToHome(PointF velocity)323         RectFSpringAnim handleSwipeUpToHome(PointF velocity) {
324             PointF velocityPxPerMs = new PointF(velocity.x, velocity.y);
325             float currentShift = mCurrentShift.value;
326             final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
327                     * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
328             float distanceToTravel = (1 - currentShift) * mTransitionDragLength;
329 
330             // we want the page's snap velocity to approximately match the velocity at
331             // which the user flings, so we scale the duration by a value near to the
332             // derivative of the scroll interpolator at zero, ie. 2.
333             long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
334             long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
335             HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory() {
336                 @Override
337                 public AnimatorPlaybackController createActivityAnimationToHome() {
338                     return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
339                 }
340 
341                 @NonNull
342                 @Override
343                 public RectF getWindowTargetRect() {
344                     int fakeHomeIconSizePx = Utilities.dpToPx(60);
345                     int fakeHomeIconLeft = getHotseatIconLeft();
346                     int fakeHomeIconTop = getHotseatIconTop();
347                     return new RectF(fakeHomeIconLeft, fakeHomeIconTop,
348                             fakeHomeIconLeft + fakeHomeIconSizePx,
349                             fakeHomeIconTop + fakeHomeIconSizePx);
350                 }
351 
352                 @Override
353                 public void update(RectF rect, float progress, float radius) {
354                     mFakeIconView.setVisibility(View.VISIBLE);
355                     mFakeIconView.update(rect, progress,
356                             1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
357                             radius,
358                             false, /* isOpening */
359                             mFakeIconView, mDp);
360                     mFakeIconView.setAlpha(1);
361                     mFakeTaskView.setAlpha(getWindowAlpha(progress));
362                     mFakePreviousTaskView.setAlpha(getWindowAlpha(progress));
363                 }
364 
365                 @Override
366                 public void onCancel() {
367                     mFakeIconView.setVisibility(View.INVISIBLE);
368                 }
369             };
370             RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift,
371                     homeAnimFactory)[0];
372             windowAnim.start(mContext, mDp, velocityPxPerMs);
373             return windowAnim;
374         }
375     }
376 
createFingerDotHomeSwipeAnimator(float fingerDotStartTranslationY)377     protected Animator createFingerDotHomeSwipeAnimator(float fingerDotStartTranslationY) {
378         Animator homeSwipeAnimator = createFingerDotSwipeUpAnimator(fingerDotStartTranslationY)
379                 .setDuration(HOME_SWIPE_ANIMATION_DURATION_MILLIS);
380 
381         homeSwipeAnimator.addListener(new AnimatorListenerAdapter() {
382             @Override
383             public void onAnimationEnd(Animator animation) {
384                 super.onAnimationEnd(animation);
385                 animateFakeTaskViewHome(
386                         new PointF(
387                                 0f,
388                                 fingerDotStartTranslationY / HOME_SWIPE_ANIMATION_DURATION_MILLIS),
389                         null);
390             }
391         });
392 
393         return homeSwipeAnimator;
394     }
395 
createFingerDotOverviewSwipeAnimator(float fingerDotStartTranslationY)396     protected Animator createFingerDotOverviewSwipeAnimator(float fingerDotStartTranslationY) {
397         Animator overviewSwipeAnimator = createFingerDotSwipeUpAnimator(fingerDotStartTranslationY)
398                 .setDuration(OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS);
399 
400         overviewSwipeAnimator.addListener(new AnimatorListenerAdapter() {
401             @Override
402             public void onAnimationEnd(Animator animation) {
403                 super.onAnimationEnd(animation);
404                 mFakePreviousTaskView.setVisibility(View.VISIBLE);
405                 onMotionPaused(true /*arbitrary value*/);
406             }
407         });
408 
409         return overviewSwipeAnimator;
410     }
411 
412 
createFingerDotSwipeUpAnimator(float fingerDotStartTranslationY)413     private Animator createFingerDotSwipeUpAnimator(float fingerDotStartTranslationY) {
414         ValueAnimator swipeAnimator = ValueAnimator.ofFloat(0f, 1f);
415 
416         swipeAnimator.addUpdateListener(valueAnimator -> {
417             float gestureProgress =
418                     -fingerDotStartTranslationY * valueAnimator.getAnimatedFraction();
419             setNavBarGestureProgress(gestureProgress);
420             mFingerDotView.setTranslationY(fingerDotStartTranslationY + gestureProgress);
421         });
422 
423         return swipeAnimator;
424     }
425 
426     private class FakeTransformParams extends TransformParams {
427 
428         @Override
createSurfaceParams(BuilderProxy proxy)429         public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) {
430             RecordingSurfaceTransaction transaction = new RecordingSurfaceTransaction();
431             proxy.onBuildTargetParams(transaction.mockProperties, null, this);
432             return transaction;
433         }
434 
435         @Override
applySurfaceParams(SurfaceTransaction params)436         public void applySurfaceParams(SurfaceTransaction params) {
437             if (params instanceof RecordingSurfaceTransaction) {
438                 MockProperties p = ((RecordingSurfaceTransaction) params).mockProperties;
439                 mFakeTaskView.setAnimationMatrix(p.matrix);
440                 mFakePreviousTaskView.setAnimationMatrix(p.matrix);
441                 mFakeTaskViewRect.set(p.windowCrop);
442                 mFakeTaskViewRadius = p.cornerRadius;
443                 mFakeTaskView.invalidateOutline();
444                 mFakePreviousTaskView.invalidateOutline();
445             }
446         }
447     }
448 }
449