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