1 /*
2  * Copyright (C) 2020 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_APPS_EDU;
19 import static com.android.launcher3.AbstractFloatingView.getOpenView;
20 import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
21 import static com.android.launcher3.LauncherState.NORMAL;
22 import static com.android.launcher3.LauncherState.OVERVIEW;
23 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
24 
25 import android.animation.ValueAnimator;
26 import android.os.SystemClock;
27 import android.util.Log;
28 import android.view.MotionEvent;
29 
30 import com.android.launcher3.AbstractFloatingView;
31 import com.android.launcher3.Launcher;
32 import com.android.launcher3.LauncherState;
33 import com.android.launcher3.testing.TestProtocol;
34 import com.android.launcher3.touch.AbstractStateChangeTouchController;
35 import com.android.launcher3.touch.SingleAxisSwipeDetector;
36 import com.android.quickstep.SystemUiProxy;
37 import com.android.quickstep.util.LayoutUtils;
38 import com.android.quickstep.views.AllAppsEduView;
39 
40 /**
41  * Touch controller for handling edge swipes in 2-button mode
42  */
43 public class TwoButtonNavbarTouchController extends AbstractStateChangeTouchController {
44 
45     private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3;
46 
47     private static final String TAG = "2BtnNavbarTouchCtrl";
48 
49     private final boolean mIsTransposed;
50 
51     // If true, we will finish the current animation instantly on second touch.
52     private boolean mFinishFastOnSecondTouch;
53 
54     private int mContinuousTouchCount = 0;
55 
TwoButtonNavbarTouchController(Launcher l)56     public TwoButtonNavbarTouchController(Launcher l) {
57         super(l, l.getDeviceProfile().isVerticalBarLayout()
58                 ? SingleAxisSwipeDetector.HORIZONTAL : SingleAxisSwipeDetector.VERTICAL);
59         mIsTransposed = l.getDeviceProfile().isVerticalBarLayout();
60     }
61 
62     @Override
canInterceptTouch(MotionEvent ev)63     protected boolean canInterceptTouch(MotionEvent ev) {
64         boolean canIntercept = canInterceptTouchInternal(ev);
65         if (!canIntercept) {
66             mContinuousTouchCount = 0;
67         }
68         return canIntercept;
69     }
70 
canInterceptTouchInternal(MotionEvent ev)71     private boolean canInterceptTouchInternal(MotionEvent ev) {
72         if (mCurrentAnimation != null) {
73             if (mFinishFastOnSecondTouch) {
74                 mCurrentAnimation.getAnimationPlayer().end();
75             }
76 
77             // If we are already animating from a previous state, we can intercept.
78             return true;
79         }
80         if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
81             return false;
82         }
83         if ((ev.getEdgeFlags() & EDGE_NAV_BAR) == 0) {
84             return false;
85         }
86         if (!mIsTransposed && mLauncher.isInState(OVERVIEW)) {
87             return true;
88         }
89         return mLauncher.isInState(NORMAL);
90     }
91 
92     @Override
onControllerInterceptTouchEvent(MotionEvent ev)93     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
94         boolean intercept = super.onControllerInterceptTouchEvent(ev);
95         return intercept;
96     }
97 
98     @Override
getTargetState(LauncherState fromState, boolean isDragTowardPositive)99     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
100         if (mIsTransposed) {
101             boolean draggingFromNav =
102                     mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
103             return draggingFromNav ? HINT_STATE_TWO_BUTTON : NORMAL;
104         } else {
105             LauncherState startState = mStartState != null ? mStartState : fromState;
106             return isDragTowardPositive ^ (startState == OVERVIEW) ? HINT_STATE_TWO_BUTTON : NORMAL;
107         }
108     }
109 
110     @Override
onReinitToState(LauncherState newToState)111     protected void onReinitToState(LauncherState newToState) {
112         super.onReinitToState(newToState);
113     }
114 
115     @Override
updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, LauncherState targetState, float velocity, boolean isFling)116     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
117             LauncherState targetState, float velocity, boolean isFling) {
118         super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
119                 velocity, isFling);
120         mFinishFastOnSecondTouch = !mIsTransposed && mFromState == NORMAL;
121 
122         if (targetState == HINT_STATE_TWO_BUTTON) {
123             // We were going to HINT_STATE_TWO_BUTTON, but end that animation immediately so we go
124             // to OVERVIEW instead.
125             animator.setDuration(0);
126         }
127     }
128 
129     @Override
getShiftRange()130     protected float getShiftRange() {
131         // Should be in sync with TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT
132         return LayoutUtils.getDefaultSwipeHeight(mLauncher, mLauncher.getDeviceProfile());
133     }
134 
135     @Override
initCurrentAnimation()136     protected float initCurrentAnimation() {
137         float range = getShiftRange();
138         long maxAccuracy = (long) (2 * range);
139         mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
140                 maxAccuracy);
141         return (mLauncher.getDeviceProfile().isSeascape() ? 1 : -1) / range;
142     }
143 
144     @Override
updateProgress(float fraction)145     protected void updateProgress(float fraction) {
146         super.updateProgress(fraction);
147 
148         // We have reached HINT_STATE, end the gesture now to go to OVERVIEW.
149         if (fraction >= 1 && mToState == HINT_STATE_TWO_BUTTON) {
150             final long now = SystemClock.uptimeMillis();
151             MotionEvent event = MotionEvent.obtain(now, now,
152                     MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
153             mDetector.onTouchEvent(event);
154             event.recycle();
155         }
156     }
157 
158     @Override
onSwipeInteractionCompleted(LauncherState targetState)159     protected void onSwipeInteractionCompleted(LauncherState targetState) {
160         super.onSwipeInteractionCompleted(targetState);
161         if (!mIsTransposed) {
162             mContinuousTouchCount++;
163         }
164         if (mStartState == NORMAL && targetState == HINT_STATE_TWO_BUTTON) {
165             SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
166         } else if (targetState == NORMAL
167                 && mContinuousTouchCount >= MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
168             mContinuousTouchCount = 0;
169             if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) {
170                 AllAppsEduView.show(mLauncher);
171             }
172         }
173         mStartState = null;
174     }
175 }
176