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
17package com.android.systemui.statusbar.phone;
18
19import static android.view.WindowManager.DOCKED_INVALID;
20import static android.view.WindowManager.DOCKED_LEFT;
21import static android.view.WindowManager.DOCKED_TOP;
22
23import android.app.ActivityManager;
24import android.content.Context;
25import android.content.res.Resources;
26import android.graphics.Canvas;
27import android.graphics.Rect;
28import android.view.MotionEvent;
29import android.view.VelocityTracker;
30import android.view.View;
31import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
32import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
33import com.android.systemui.Dependency;
34import com.android.systemui.R;
35import com.android.systemui.RecentsComponent;
36import com.android.systemui.SysUiServiceProvider;
37import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
38import com.android.systemui.stackdivider.Divider;
39import com.android.systemui.tuner.TunerService;
40
41/**
42 * Class to detect gestures on the navigation bar.
43 */
44public 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
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
92    public void destroy() {
93        Dependency.get(TunerService.class).removeTunable(this);
94    }
95
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
104    public void setBarState(boolean isVertical, boolean isRTL) {
105        mIsVertical = isVertical;
106        mQuickStepController.setBarState(isVertical, isRTL);
107    }
108
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
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
135    public void onDraw(Canvas canvas) {
136        mQuickStepController.onDraw(canvas);
137    }
138
139    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
140        mQuickStepController.onLayout(changed, left, top, right, bottom);
141    }
142
143    public void onDarkIntensityChange(float intensity) {
144        mQuickStepController.onDarkIntensityChange(intensity);
145    }
146
147    public void onNavigationButtonLongPress(View v) {
148        mQuickStepController.onNavigationButtonLongPress(v);
149    }
150
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
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
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
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
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
277    private boolean canHandleGestures() {
278        return !mIsInScreenPinning && !mStatusBar.isKeyguardShowing()
279                && !mNotificationsVisibleOnDown;
280    }
281
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
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