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