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