• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
19 import static com.android.launcher3.LauncherState.ALL_APPS;
20 import static com.android.launcher3.LauncherState.OVERVIEW;
21 import static com.android.launcher3.anim.Interpolators.DEACCEL;
22 import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
23 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
24 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
25 
26 import android.animation.ValueAnimator;
27 import android.view.Surface;
28 
29 import com.android.launcher3.Launcher;
30 import com.android.launcher3.LauncherAnimUtils;
31 import com.android.launcher3.R;
32 import com.android.launcher3.allapps.AllAppsTransitionController;
33 import com.android.launcher3.allapps.DiscoveryBounce;
34 import com.android.launcher3.anim.AnimatorPlaybackController;
35 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
36 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
37 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
38 import com.android.launcher3.util.FlingBlockCheck;
39 import com.android.quickstep.util.RemoteAnimationTargetSet;
40 import com.android.quickstep.views.RecentsView;
41 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
42 import com.android.systemui.shared.system.TransactionCompat;
43 
44 /**
45  * Utility class to handle long swipe from an app.
46  * This assumes the presence of Launcher activity as long swipe is not supported on the
47  * fallback activity.
48  */
49 public class LongSwipeHelper {
50 
51     private static final float SWIPE_DURATION_MULTIPLIER =
52             Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS));
53 
54     private final Launcher mLauncher;
55     private final RemoteAnimationTargetSet mTargetSet;
56 
57     private float mMaxSwipeDistance = 1;
58     private AnimatorPlaybackController mAnimator;
59     private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
60 
LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet)61     LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) {
62         mLauncher = launcher;
63         mTargetSet = targetSet;
64         init();
65     }
66 
init()67     private void init() {
68         setTargetAlpha(0, true);
69         mFlingBlockCheck.blockFling();
70 
71         // Init animations
72         AllAppsTransitionController controller = mLauncher.getAllAppsController();
73         // TODO: Scale it down so that we can reach all-apps in screen space
74         mMaxSwipeDistance = Math.max(1, controller.getProgress() * controller.getShiftRange());
75         mAnimator = mLauncher.getStateManager()
76                 .createAnimationToNewWorkspace(ALL_APPS, Math.round(2 * mMaxSwipeDistance));
77         mAnimator.dispatchOnStart();
78     }
79 
onMove(float displacement)80     public void onMove(float displacement) {
81         mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
82         mFlingBlockCheck.onEvent();
83     }
84 
destroy()85     public void destroy() {
86         // TODO: We can probably also hide the task view
87         setTargetAlpha(1, false);
88 
89         mLauncher.getStateManager().goToState(OVERVIEW, false);
90     }
91 
end(float velocity, boolean isFling, Runnable callback)92     public void end(float velocity, boolean isFling, Runnable callback) {
93         long duration = MAX_SWIPE_DURATION;
94 
95         final float currentFraction = mAnimator.getProgressFraction();
96         final boolean toAllApps;
97         float endProgress;
98 
99         boolean blockedFling = isFling && mFlingBlockCheck.isBlocked();
100         if (blockedFling) {
101             isFling = false;
102         }
103 
104         if (!isFling) {
105             toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS;
106             endProgress = toAllApps ? 1 : 0;
107 
108             long expectedDuration = Math.abs(Math.round((endProgress - currentFraction)
109                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
110             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
111         } else {
112             toAllApps = velocity < 0;
113             endProgress = toAllApps ? 1 : 0;
114 
115             float minFlingVelocity = mLauncher.getResources()
116                     .getDimension(R.dimen.quickstep_fling_min_velocity);
117             if (Math.abs(velocity) > minFlingVelocity && mMaxSwipeDistance > 0) {
118                 float distanceToTravel = (endProgress - currentFraction) * mMaxSwipeDistance;
119 
120                 // we want the page's snap velocity to approximately match the velocity at
121                 // which the user flings, so we scale the duration by a value near to the
122                 // derivative of the scroll interpolator at zero, ie. 2.
123                 long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity));
124                 duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
125             }
126         }
127 
128         if (blockedFling && !toAllApps) {
129             duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
130         }
131         final boolean finalIsFling = isFling;
132         mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
133         ValueAnimator animator = mAnimator.getAnimationPlayer();
134         animator.setDuration(duration).setInterpolator(DEACCEL);
135         animator.setFloatValues(currentFraction, endProgress);
136         animator.start();
137     }
138 
setTargetAlpha(float alpha, boolean defer)139     private void setTargetAlpha(float alpha, boolean defer) {
140         final Surface surface = getSurface(mLauncher.getDragLayer());
141         final long frameNumber = defer && surface != null ? getNextFrameNumber(surface) : -1;
142         if (defer) {
143             if (frameNumber == -1) {
144                 defer = false;
145             } else {
146                 mLauncher.getDragLayer().invalidate();
147             }
148         }
149 
150         TransactionCompat transaction = new TransactionCompat();
151         for (RemoteAnimationTargetCompat app : mTargetSet.apps) {
152             if (!(app.isNotInRecents
153                     || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
154                 transaction.setAlpha(app.leash, alpha);
155                 if (defer) {
156                     transaction.deferTransactionUntil(app.leash, surface, frameNumber);
157                 }
158             }
159         }
160         transaction.setEarlyWakeup();
161         transaction.apply();
162     }
163 
onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback)164     private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) {
165         mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
166         if (!toAllApps) {
167             DiscoveryBounce.showForOverviewIfNeeded(mLauncher);
168             mLauncher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(true);
169         }
170 
171         mLauncher.getUserEventDispatcher().logStateChangeAction(
172                 isFling ? Touch.FLING : Touch.SWIPE, Direction.UP,
173                 ContainerType.NAVBAR, ContainerType.APP,
174                 toAllApps ? ContainerType.ALLAPPS : ContainerType.TASKSWITCHER,
175                 0);
176 
177         callback.run();
178     }
179 }
180