Home | History | Annotate | Download | only in phone
      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 
     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