1 /*
2  * Copyright (C) 2014 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 
17 package com.android.systemui.statusbar.phone;
18 
19 import static android.view.WindowManager.DOCKED_INVALID;
20 import static android.view.WindowManager.DOCKED_LEFT;
21 import static android.view.WindowManager.DOCKED_TOP;
22 
23 import android.app.ActivityManager;
24 import android.content.Context;
25 import android.content.res.Resources;
26 import android.graphics.Canvas;
27 import android.graphics.Rect;
28 import android.view.MotionEvent;
29 import android.view.VelocityTracker;
30 import android.view.View;
31 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
32 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
33 import com.android.systemui.Dependency;
34 import com.android.systemui.R;
35 import com.android.systemui.RecentsComponent;
36 import com.android.systemui.SysUiServiceProvider;
37 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
38 import com.android.systemui.stackdivider.Divider;
39 import com.android.systemui.tuner.TunerService;
40 
41 /**
42  * Class to detect gestures on the navigation bar.
43  */
44 public class NavigationBarGestureHelper implements TunerService.Tunable, GestureHelper {
45 
46     private static final String TAG = "NavBarGestureHelper";
47     private static final String KEY_DOCK_WINDOW_GESTURE = "overview_nav_bar_gesture";
48     /**
49      * When dragging from the navigation bar, we drag in recents.
50      */
51     public static final int DRAG_MODE_NONE = -1;
52 
53     /**
54      * When dragging from the navigation bar, we drag in recents.
55      */
56     public static final int DRAG_MODE_RECENTS = 0;
57 
58     /**
59      * When dragging from the navigation bar, we drag the divider.
60      */
61     public static final int DRAG_MODE_DIVIDER = 1;
62 
63     private RecentsComponent mRecentsComponent;
64     private Divider mDivider;
65     private Context mContext;
66     private NavigationBarView mNavigationBarView;
67     private boolean mIsVertical;
68 
69     private final QuickStepController mQuickStepController;
70     private final int mScrollTouchSlop;
71     private final StatusBar mStatusBar;
72     private int mTouchDownX;
73     private int mTouchDownY;
74     private boolean mDownOnRecents;
75     private VelocityTracker mVelocityTracker;
76     private boolean mIsInScreenPinning;
77     private boolean mNotificationsVisibleOnDown;
78 
79     private boolean mDockWindowEnabled;
80     private boolean mDockWindowTouchSlopExceeded;
81     private int mDragMode;
82 
NavigationBarGestureHelper(Context context)83     public NavigationBarGestureHelper(Context context) {
84         mContext = context;
85         mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
86         Resources r = context.getResources();
87         mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
88         mQuickStepController = new QuickStepController(context);
89         Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
90     }
91 
destroy()92     public void destroy() {
93         Dependency.get(TunerService.class).removeTunable(this);
94     }
95 
setComponents(RecentsComponent recentsComponent, Divider divider, NavigationBarView navigationBarView)96     public void setComponents(RecentsComponent recentsComponent, Divider divider,
97             NavigationBarView navigationBarView) {
98         mRecentsComponent = recentsComponent;
99         mDivider = divider;
100         mNavigationBarView = navigationBarView;
101         mQuickStepController.setComponents(mNavigationBarView);
102     }
103 
setBarState(boolean isVertical, boolean isRTL)104     public void setBarState(boolean isVertical, boolean isRTL) {
105         mIsVertical = isVertical;
106         mQuickStepController.setBarState(isVertical, isRTL);
107     }
108 
onInterceptTouchEvent(MotionEvent event)109     public boolean onInterceptTouchEvent(MotionEvent event) {
110         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
111             mIsInScreenPinning = mNavigationBarView.inScreenPinning();
112             mNotificationsVisibleOnDown = !mStatusBar.isPresenterFullyCollapsed();
113         }
114         if (!canHandleGestures()) {
115             return false;
116         }
117         boolean result = mQuickStepController.onInterceptTouchEvent(event);
118         if (mDockWindowEnabled) {
119             result |= interceptDockWindowEvent(event);
120         }
121         return result;
122     }
123 
onTouchEvent(MotionEvent event)124     public boolean onTouchEvent(MotionEvent event) {
125         if (!canHandleGestures()) {
126             return false;
127         }
128         boolean result = mQuickStepController.onTouchEvent(event);
129         if (mDockWindowEnabled) {
130             result |= handleDockWindowEvent(event);
131         }
132         return result;
133     }
134 
onDraw(Canvas canvas)135     public void onDraw(Canvas canvas) {
136         mQuickStepController.onDraw(canvas);
137     }
138 
onLayout(boolean changed, int left, int top, int right, int bottom)139     public void onLayout(boolean changed, int left, int top, int right, int bottom) {
140         mQuickStepController.onLayout(changed, left, top, right, bottom);
141     }
142 
onDarkIntensityChange(float intensity)143     public void onDarkIntensityChange(float intensity) {
144         mQuickStepController.onDarkIntensityChange(intensity);
145     }
146 
onNavigationButtonLongPress(View v)147     public void onNavigationButtonLongPress(View v) {
148         mQuickStepController.onNavigationButtonLongPress(v);
149     }
150 
interceptDockWindowEvent(MotionEvent event)151     private boolean interceptDockWindowEvent(MotionEvent event) {
152         switch (event.getActionMasked()) {
153             case MotionEvent.ACTION_DOWN:
154                 handleDragActionDownEvent(event);
155                 break;
156             case MotionEvent.ACTION_MOVE:
157                 return handleDragActionMoveEvent(event);
158             case MotionEvent.ACTION_UP:
159             case MotionEvent.ACTION_CANCEL:
160                 handleDragActionUpEvent(event);
161                 break;
162         }
163         return false;
164     }
165 
handleDockWindowEvent(MotionEvent event)166     private boolean handleDockWindowEvent(MotionEvent event) {
167         switch (event.getActionMasked()) {
168             case MotionEvent.ACTION_DOWN:
169                 handleDragActionDownEvent(event);
170                 break;
171             case MotionEvent.ACTION_MOVE:
172                 handleDragActionMoveEvent(event);
173                 break;
174             case MotionEvent.ACTION_UP:
175             case MotionEvent.ACTION_CANCEL:
176                 handleDragActionUpEvent(event);
177                 break;
178         }
179         return true;
180     }
181 
handleDragActionDownEvent(MotionEvent event)182     private void handleDragActionDownEvent(MotionEvent event) {
183         mVelocityTracker = VelocityTracker.obtain();
184         mVelocityTracker.addMovement(event);
185         mDockWindowTouchSlopExceeded = false;
186         mTouchDownX = (int) event.getX();
187         mTouchDownY = (int) event.getY();
188 
189         if (mNavigationBarView != null) {
190             View recentsButton = mNavigationBarView.getRecentsButton().getCurrentView();
191             if (recentsButton != null) {
192                 mDownOnRecents = mTouchDownX >= recentsButton.getLeft()
193                         && mTouchDownX <= recentsButton.getRight()
194                         && mTouchDownY >= recentsButton.getTop()
195                         && mTouchDownY <= recentsButton.getBottom();
196             } else {
197                 mDownOnRecents = false;
198             }
199         }
200     }
201 
handleDragActionMoveEvent(MotionEvent event)202     private boolean handleDragActionMoveEvent(MotionEvent event) {
203         mVelocityTracker.addMovement(event);
204         int x = (int) event.getX();
205         int y = (int) event.getY();
206         int xDiff = Math.abs(x - mTouchDownX);
207         int yDiff = Math.abs(y - mTouchDownY);
208         if (mDivider == null || mRecentsComponent == null) {
209             return false;
210         }
211         if (!mDockWindowTouchSlopExceeded) {
212             boolean touchSlopExceeded = !mIsVertical
213                     ? yDiff > mScrollTouchSlop && yDiff > xDiff
214                     : xDiff > mScrollTouchSlop && xDiff > yDiff;
215             if (mDownOnRecents && touchSlopExceeded
216                     && mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
217                 Rect initialBounds = null;
218                 int dragMode = calculateDragMode();
219                 int createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
220                 if (dragMode == DRAG_MODE_DIVIDER) {
221                     initialBounds = new Rect();
222                     mDivider.getView().calculateBoundsForPosition(mIsVertical
223                                     ? (int) event.getRawX()
224                                     : (int) event.getRawY(),
225                             mDivider.getView().isHorizontalDivision()
226                                     ? DOCKED_TOP
227                                     : DOCKED_LEFT,
228                             initialBounds);
229                 } else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX
230                         < mContext.getResources().getDisplayMetrics().widthPixels / 2) {
231                     createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
232                 }
233                 boolean docked = mRecentsComponent.splitPrimaryTask(dragMode, createMode,
234                         initialBounds, MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
235                 if (docked) {
236                     mDragMode = dragMode;
237                     if (mDragMode == DRAG_MODE_DIVIDER) {
238                         mDivider.getView().startDragging(false /* animate */, true /* touching*/);
239                     }
240                     mDockWindowTouchSlopExceeded = true;
241                     return true;
242                 }
243             }
244         } else {
245             if (mDragMode == DRAG_MODE_DIVIDER) {
246                 int position = !mIsVertical ? (int) event.getRawY() : (int) event.getRawX();
247                 SnapTarget snapTarget = mDivider.getView().getSnapAlgorithm()
248                         .calculateSnapTarget(position, 0f /* velocity */, false /* hardDismiss */);
249                 mDivider.getView().resizeStack(position, snapTarget.position, snapTarget);
250             } else if (mDragMode == DRAG_MODE_RECENTS) {
251                 mRecentsComponent.onDraggingInRecents(event.getRawY());
252             }
253         }
254         return false;
255     }
256 
handleDragActionUpEvent(MotionEvent event)257     private void handleDragActionUpEvent(MotionEvent event) {
258         mVelocityTracker.addMovement(event);
259         mVelocityTracker.computeCurrentVelocity(1000);
260         if (mDockWindowTouchSlopExceeded && mDivider != null && mRecentsComponent != null) {
261             if (mDragMode == DRAG_MODE_DIVIDER) {
262                 mDivider.getView().stopDragging(mIsVertical
263                                 ? (int) event.getRawX()
264                                 : (int) event.getRawY(),
265                         mIsVertical
266                                 ? mVelocityTracker.getXVelocity()
267                                 : mVelocityTracker.getYVelocity(),
268                         true /* avoidDismissStart */, false /* logMetrics */);
269             } else if (mDragMode == DRAG_MODE_RECENTS) {
270                 mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
271             }
272         }
273         mVelocityTracker.recycle();
274         mVelocityTracker = null;
275     }
276 
canHandleGestures()277     private boolean canHandleGestures() {
278         return !mIsInScreenPinning && !mStatusBar.isKeyguardShowing()
279                 && !mNotificationsVisibleOnDown;
280     }
281 
calculateDragMode()282     private int calculateDragMode() {
283         if (mIsVertical && !mDivider.getView().isHorizontalDivision()) {
284             return DRAG_MODE_DIVIDER;
285         }
286         if (!mIsVertical && mDivider.getView().isHorizontalDivision()) {
287             return DRAG_MODE_DIVIDER;
288         }
289         return DRAG_MODE_RECENTS;
290     }
291 
292     @Override
onTuningChanged(String key, String newValue)293     public void onTuningChanged(String key, String newValue) {
294         switch (key) {
295             case KEY_DOCK_WINDOW_GESTURE:
296                 mDockWindowEnabled = newValue != null && (Integer.parseInt(newValue) != 0);
297                 break;
298         }
299     }
300 }
301