• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.touchcontrollers;
17 
18 import static com.android.app.animation.Interpolators.DECELERATE_3;
19 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
20 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
21 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
22 import static com.android.launcher3.LauncherAnimUtils.newSingleUseCancelListener;
23 import static com.android.launcher3.LauncherState.ALL_APPS;
24 import static com.android.launcher3.LauncherState.NORMAL;
25 import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
26 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
27 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
28 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
29 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
30 import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
31 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
32 
33 import android.animation.AnimatorSet;
34 import android.animation.ValueAnimator;
35 import android.view.MotionEvent;
36 import android.view.animation.Interpolator;
37 
38 import com.android.launcher3.AbstractFloatingView;
39 import com.android.launcher3.Launcher;
40 import com.android.launcher3.LauncherState;
41 import com.android.launcher3.R;
42 import com.android.launcher3.Utilities;
43 import com.android.launcher3.allapps.AllAppsTransitionController;
44 import com.android.launcher3.anim.AnimatorPlaybackController;
45 import com.android.launcher3.anim.PendingAnimation;
46 import com.android.launcher3.compat.AccessibilityManagerCompat;
47 import com.android.launcher3.config.FeatureFlags;
48 import com.android.launcher3.statemanager.StateManager;
49 import com.android.launcher3.touch.SingleAxisSwipeDetector;
50 import com.android.launcher3.util.DisplayController;
51 import com.android.launcher3.util.TouchController;
52 import com.android.quickstep.TaskUtils;
53 import com.android.quickstep.util.AnimatorControllerWithResistance;
54 import com.android.quickstep.util.OverviewToHomeAnim;
55 import com.android.quickstep.views.RecentsView;
56 
57 import java.util.function.BiConsumer;
58 
59 /**
60  * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
61  */
62 public class NavBarToHomeTouchController implements TouchController,
63         SingleAxisSwipeDetector.Listener {
64 
65     private static final Interpolator PULLBACK_INTERPOLATOR = DECELERATE_3;
66     // The min amount of overview scrim we keep during the transition.
67     private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
68 
69     private final Launcher mLauncher;
70     private final BiConsumer<AnimatorSet, Long> mCancelSplitRunnable;
71     private final SingleAxisSwipeDetector mSwipeDetector;
72     private final float mPullbackDistance;
73 
74     private boolean mNoIntercept;
75     private LauncherState mStartState;
76     private LauncherState mEndState = NORMAL;
77     private AnimatorPlaybackController mCurrentAnimation;
78 
79     /**
80      * @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
81      *                            Animation should be added to the provided AnimatorSet
82      */
NavBarToHomeTouchController(Launcher launcher, BiConsumer<AnimatorSet, Long> cancelSplitRunnable)83     public NavBarToHomeTouchController(Launcher launcher,
84             BiConsumer<AnimatorSet, Long> cancelSplitRunnable) {
85         mLauncher = launcher;
86         mCancelSplitRunnable = cancelSplitRunnable;
87         mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
88                 SingleAxisSwipeDetector.VERTICAL);
89         mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
90     }
91 
92     @Override
onControllerInterceptTouchEvent(MotionEvent ev)93     public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
94         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
95             mStartState = mLauncher.getStateManager().getState();
96             mNoIntercept = !canInterceptTouch(ev);
97             if (mNoIntercept) {
98                 return false;
99             }
100             mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE,
101                     false /* ignoreSlop */);
102         }
103 
104         if (mNoIntercept) {
105             return false;
106         }
107 
108         onControllerTouchEvent(ev);
109         return mSwipeDetector.isDraggingOrSettling();
110     }
111 
canInterceptTouch(MotionEvent ev)112     private boolean canInterceptTouch(MotionEvent ev) {
113         if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
114                 == THREE_BUTTONS) {
115             return false;
116         }
117         boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
118         if (!cameFromNavBar) {
119             return false;
120         }
121         if (mStartState.isRecentsViewVisible || mStartState == ALL_APPS) {
122             return true;
123         }
124         int typeToClose = TYPE_ALL & ~TYPE_ALL_APPS_EDU;
125         if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
126             return true;
127         }
128         return false;
129     }
130 
131     @Override
onControllerTouchEvent(MotionEvent ev)132     public final boolean onControllerTouchEvent(MotionEvent ev) {
133         return mSwipeDetector.onTouchEvent(ev);
134     }
135 
getShiftRange()136     private float getShiftRange() {
137         return mLauncher.getDeviceProfile().heightPx;
138     }
139 
140     @Override
onDragStart(boolean start, float startDisplacement)141     public void onDragStart(boolean start, float startDisplacement) {
142         initCurrentAnimation();
143     }
144 
initCurrentAnimation()145     private void initCurrentAnimation() {
146         long accuracy = (long) (getShiftRange() * 2);
147         final PendingAnimation builder = new PendingAnimation(accuracy);
148         if (mStartState.isRecentsViewVisible) {
149             RecentsView recentsView = mLauncher.getOverviewPanel();
150             AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
151                     builder);
152 
153             builder.addOnFrameCallback(recentsView::redrawLiveTile);
154 
155             AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
156         } else if (mStartState == ALL_APPS) {
157             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
158             builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_TRANSLATION,
159                     -mPullbackDistance, PULLBACK_INTERPOLATOR);
160             builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_ALPHA,
161                     0.5f, PULLBACK_INTERPOLATOR);
162         }
163         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
164         if (topView != null) {
165             topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder);
166         }
167         mCurrentAnimation = builder.createPlaybackController();
168         mCurrentAnimation.getTarget().addListener(newSingleUseCancelListener(this::clearState));
169     }
170 
clearState()171     private void clearState() {
172         mCurrentAnimation = null;
173         mSwipeDetector.finishedScrolling();
174         mSwipeDetector.setDetectableScrollConditions(0, false);
175     }
176 
177     @Override
onDrag(float displacement)178     public boolean onDrag(float displacement) {
179         // Only allow swipe up.
180         displacement = Math.min(0, displacement);
181         float progress = Utilities.getProgress(displacement, 0, getShiftRange());
182         mCurrentAnimation.setPlayFraction(progress);
183         return true;
184     }
185 
186     @Override
onDragEnd(float velocity)187     public void onDragEnd(float velocity) {
188         boolean fling = mSwipeDetector.isFling(velocity);
189         float progress = mCurrentAnimation.getProgressFraction();
190         float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress);
191         boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
192                 || (velocity < 0 && fling);
193         if (success) {
194             RecentsView recentsView = mLauncher.getOverviewPanel();
195             recentsView.switchToScreenshot(null,
196                     () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
197             if (mStartState.isRecentsViewVisible) {
198                 Runnable onReachedHome = () -> {
199                     StateManager.StateListener<LauncherState> listener =
200                             new StateManager.StateListener<>() {
201                                 @Override
202                                 public void onStateTransitionComplete(LauncherState finalState) {
203                                     mLauncher.onStateTransitionCompletedAfterSwipeToHome(
204                                             finalState);
205                                     mLauncher.getStateManager().removeStateListener(this);
206                                 }
207                             };
208                     mLauncher.getStateManager().addStateListener(listener);
209                     onSwipeInteractionCompleted(mEndState);
210                 };
211                 new OverviewToHomeAnim(mLauncher, onReachedHome,
212                         FeatureFlags.enableSplitContextually()
213                                 ? mCancelSplitRunnable
214                                 : null)
215                         .animateWithVelocity(velocity);
216             } else {
217                 mLauncher.getStateManager().goToState(mEndState, true,
218                         forSuccessCallback(() -> onSwipeInteractionCompleted(mEndState)));
219             }
220             if (mStartState != mEndState) {
221                 logHomeGesture();
222             }
223             AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
224             if (topOpenView != null) {
225                 AbstractFloatingView.closeAllOpenViews(mLauncher);
226                 // TODO: add to WW log
227             }
228             TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
229         } else {
230             // Quickly return to the state we came from (we didn't move far).
231             ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
232             anim.setFloatValues(progress, 0);
233             anim.addListener(forSuccessCallback(() -> onSwipeInteractionCompleted(mStartState)));
234             anim.setDuration(80).start();
235         }
236     }
237 
onSwipeInteractionCompleted(LauncherState targetState)238     private void onSwipeInteractionCompleted(LauncherState targetState) {
239         clearState();
240         mLauncher.getStateManager().goToState(targetState, false /* animated */);
241         AccessibilityManagerCompat.sendStateEventToTest(mLauncher, targetState.ordinal);
242     }
243 
logHomeGesture()244     private void logHomeGesture() {
245         mLauncher.getStatsLogManager().logger()
246                 .withSrcState(mStartState.statsLogOrdinal)
247                 .withDstState(mEndState.statsLogOrdinal)
248                 .log(LAUNCHER_HOME_GESTURE);
249     }
250 }
251