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