• 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.launcher3.uioverrides;
17 
18 import static com.android.launcher3.LauncherState.ALL_APPS;
19 import static com.android.launcher3.LauncherState.NORMAL;
20 import static com.android.launcher3.LauncherState.OVERVIEW;
21 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
22 import static com.android.launcher3.anim.Interpolators.LINEAR;
23 
24 import android.animation.TimeInterpolator;
25 import android.animation.ValueAnimator;
26 import android.view.MotionEvent;
27 import android.view.animation.Interpolator;
28 import android.view.animation.OvershootInterpolator;
29 
30 import com.android.launcher3.AbstractFloatingView;
31 import com.android.launcher3.DeviceProfile;
32 import com.android.launcher3.Launcher;
33 import com.android.launcher3.LauncherState;
34 import com.android.launcher3.LauncherStateManager.AnimationComponents;
35 import com.android.launcher3.anim.AnimatorPlaybackController;
36 import com.android.launcher3.anim.AnimatorSetBuilder;
37 import com.android.launcher3.anim.Interpolators;
38 import com.android.launcher3.touch.AbstractStateChangeTouchController;
39 import com.android.launcher3.touch.SwipeDetector;
40 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
41 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
42 import com.android.quickstep.RecentsModel;
43 import com.android.quickstep.TouchInteractionService;
44 import com.android.quickstep.views.RecentsView;
45 import com.android.quickstep.views.TaskView;
46 
47 /**
48  * Touch controller for handling various state transitions in portrait UI.
49  */
50 public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
51 
52     private static final String TAG = "PortraitStatesTouchCtrl";
53 
54     private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
55 
56     // If true, we will finish the current animation instantly on second touch.
57     private boolean mFinishFastOnSecondTouch;
58 
59 
PortraitStatesTouchController(Launcher l)60     public PortraitStatesTouchController(Launcher l) {
61         super(l, SwipeDetector.VERTICAL);
62     }
63 
64     @Override
canInterceptTouch(MotionEvent ev)65     protected boolean canInterceptTouch(MotionEvent ev) {
66         if (mCurrentAnimation != null) {
67             if (mFinishFastOnSecondTouch) {
68                 // TODO: Animate to finish instead.
69                 mCurrentAnimation.getAnimationPlayer().end();
70             }
71 
72             // If we are already animating from a previous state, we can intercept.
73             return true;
74         }
75         if (mLauncher.isInState(ALL_APPS)) {
76             // In all-apps only listen if the container cannot scroll itself
77             if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
78                 return false;
79             }
80         } else {
81             // For all other states, only listen if the event originated below the hotseat height
82             DeviceProfile dp = mLauncher.getDeviceProfile();
83             int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
84             if (ev.getY() < (mLauncher.getDragLayer().getHeight() - hotseatHeight)) {
85                 return false;
86             }
87         }
88         if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
89             return false;
90         }
91         return true;
92     }
93 
94     @Override
getTargetState(LauncherState fromState, boolean isDragTowardPositive)95     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
96         if (fromState == ALL_APPS && !isDragTowardPositive) {
97             // Should swipe down go to OVERVIEW instead?
98             return TouchInteractionService.isConnected() ?
99                     mLauncher.getStateManager().getLastState() : NORMAL;
100         } else if (fromState == OVERVIEW) {
101             return isDragTowardPositive ? ALL_APPS : NORMAL;
102         } else if (fromState == NORMAL && isDragTowardPositive) {
103             return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
104         }
105         return fromState;
106     }
107 
108     @Override
getLogContainerTypeForNormalState()109     protected int getLogContainerTypeForNormalState() {
110         return ContainerType.HOTSEAT;
111     }
112 
getNormalToOverviewAnimation()113     private AnimatorSetBuilder getNormalToOverviewAnimation() {
114         mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
115 
116         AnimatorSetBuilder builder = new AnimatorSetBuilder();
117         builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
118 
119         return builder;
120     }
121 
122     @Override
initCurrentAnimation(@nimationComponents int animComponents)123     protected float initCurrentAnimation(@AnimationComponents int animComponents) {
124         float range = getShiftRange();
125         long maxAccuracy = (long) (2 * range);
126 
127         float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
128         float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
129 
130         float totalShift = endVerticalShift - startVerticalShift;
131 
132         final AnimatorSetBuilder builder;
133 
134         if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
135             builder = getNormalToOverviewAnimation();
136         } else {
137             builder = new AnimatorSetBuilder();
138         }
139 
140         cancelPendingAnim();
141 
142         RecentsView recentsView = mLauncher.getOverviewPanel();
143         TaskView taskView = (TaskView) recentsView.getChildAt(recentsView.getNextPage());
144         if (recentsView.shouldSwipeDownLaunchApp() && mFromState == OVERVIEW && mToState == NORMAL
145                 && taskView != null) {
146             mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
147             mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
148 
149             Runnable onCancelRunnable = () -> {
150                 cancelPendingAnim();
151                 clearState();
152             };
153             mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
154                     onCancelRunnable);
155             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
156         } else {
157             mCurrentAnimation = mLauncher.getStateManager()
158                     .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
159                             animComponents);
160         }
161 
162         if (totalShift == 0) {
163             totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
164                     * OverviewState.getDefaultSwipeHeight(mLauncher);
165         }
166         return 1 / totalShift;
167     }
168 
cancelPendingAnim()169     private void cancelPendingAnim() {
170         if (mPendingAnimation != null) {
171             mPendingAnimation.finish(false, Touch.SWIPE);
172             mPendingAnimation = null;
173         }
174     }
175 
176     @Override
updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, LauncherState targetState, float velocity, boolean isFling)177     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
178             LauncherState targetState, float velocity, boolean isFling) {
179         super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
180                 velocity, isFling);
181         handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
182     }
183 
handleFirstSwipeToOverview(final ValueAnimator animator, final long expectedDuration, final LauncherState targetState, final float velocity, final boolean isFling)184     private void handleFirstSwipeToOverview(final ValueAnimator animator,
185             final long expectedDuration, final LauncherState targetState, final float velocity,
186             final boolean isFling) {
187         if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
188             mFinishFastOnSecondTouch = true;
189             if (isFling && expectedDuration != 0) {
190                 // Update all apps interpolator to add a bit of overshoot starting from currFraction
191                 final float currFraction = mCurrentAnimation.getProgressFraction();
192                 mAllAppsInterpolatorWrapper.baseInterpolator = Interpolators.clampToProgress(
193                         new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)), currFraction, 1);
194                 animator.setDuration(Math.min(expectedDuration, ATOMIC_DURATION))
195                         .setInterpolator(LINEAR);
196             }
197         } else {
198             mFinishFastOnSecondTouch = false;
199         }
200     }
201 
202     @Override
onSwipeInteractionCompleted(LauncherState targetState, int logAction)203     protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
204         super.onSwipeInteractionCompleted(targetState, logAction);
205         if (mStartState == NORMAL && targetState == OVERVIEW) {
206             RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG);
207         }
208     }
209 
210     private static class InterpolatorWrapper implements Interpolator {
211 
212         public TimeInterpolator baseInterpolator = LINEAR;
213 
214         @Override
getInterpolation(float v)215         public float getInterpolation(float v) {
216             return baseInterpolator.getInterpolation(v);
217         }
218     }
219 }
220