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 android.app.ActivityManager; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.graphics.Rect; 23 import android.view.GestureDetector; 24 import android.view.MotionEvent; 25 import android.view.VelocityTracker; 26 import android.view.View; 27 import android.view.ViewConfiguration; 28 29 import com.android.internal.logging.MetricsLogger; 30 import com.android.internal.logging.MetricsProto.MetricsEvent; 31 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; 32 import com.android.systemui.R; 33 import com.android.systemui.RecentsComponent; 34 import com.android.systemui.stackdivider.Divider; 35 import com.android.systemui.tuner.TunerService; 36 37 import static android.view.WindowManager.DOCKED_INVALID; 38 import static android.view.WindowManager.DOCKED_LEFT; 39 import static android.view.WindowManager.DOCKED_TOP; 40 41 /** 42 * Class to detect gestures on the navigation bar. 43 */ 44 public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener 45 implements TunerService.Tunable { 46 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 private boolean mIsRTL; 69 70 private final GestureDetector mTaskSwitcherDetector; 71 private final int mScrollTouchSlop; 72 private final int mMinFlingVelocity; 73 private int mTouchDownX; 74 private int mTouchDownY; 75 private boolean mDownOnRecents; 76 private VelocityTracker mVelocityTracker; 77 78 private boolean mDockWindowEnabled; 79 private boolean mDockWindowTouchSlopExceeded; 80 private int mDragMode; 81 NavigationBarGestureHelper(Context context)82 public NavigationBarGestureHelper(Context context) { 83 mContext = context; 84 ViewConfiguration configuration = ViewConfiguration.get(context); 85 Resources r = context.getResources(); 86 mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance); 87 mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity(); 88 mTaskSwitcherDetector = new GestureDetector(context, this); 89 TunerService.get(context).addTunable(this, KEY_DOCK_WINDOW_GESTURE); 90 } 91 setComponents(RecentsComponent recentsComponent, Divider divider, NavigationBarView navigationBarView)92 public void setComponents(RecentsComponent recentsComponent, Divider divider, 93 NavigationBarView navigationBarView) { 94 mRecentsComponent = recentsComponent; 95 mDivider = divider; 96 mNavigationBarView = navigationBarView; 97 } 98 setBarState(boolean isVertical, boolean isRTL)99 public void setBarState(boolean isVertical, boolean isRTL) { 100 mIsVertical = isVertical; 101 mIsRTL = isRTL; 102 } 103 onInterceptTouchEvent(MotionEvent event)104 public boolean onInterceptTouchEvent(MotionEvent event) { 105 // If we move more than a fixed amount, then start capturing for the 106 // task switcher detector 107 mTaskSwitcherDetector.onTouchEvent(event); 108 int action = event.getAction(); 109 switch (action & MotionEvent.ACTION_MASK) { 110 case MotionEvent.ACTION_DOWN: { 111 mTouchDownX = (int) event.getX(); 112 mTouchDownY = (int) event.getY(); 113 break; 114 } 115 case MotionEvent.ACTION_MOVE: { 116 int x = (int) event.getX(); 117 int y = (int) event.getY(); 118 int xDiff = Math.abs(x - mTouchDownX); 119 int yDiff = Math.abs(y - mTouchDownY); 120 boolean exceededTouchSlop = !mIsVertical 121 ? xDiff > mScrollTouchSlop && xDiff > yDiff 122 : yDiff > mScrollTouchSlop && yDiff > xDiff; 123 if (exceededTouchSlop) { 124 return true; 125 } 126 break; 127 } 128 case MotionEvent.ACTION_CANCEL: 129 case MotionEvent.ACTION_UP: 130 break; 131 } 132 return mDockWindowEnabled && interceptDockWindowEvent(event); 133 } 134 interceptDockWindowEvent(MotionEvent event)135 private boolean interceptDockWindowEvent(MotionEvent event) { 136 switch (event.getActionMasked()) { 137 case MotionEvent.ACTION_DOWN: 138 handleDragActionDownEvent(event); 139 break; 140 case MotionEvent.ACTION_MOVE: 141 return handleDragActionMoveEvent(event); 142 case MotionEvent.ACTION_UP: 143 case MotionEvent.ACTION_CANCEL: 144 handleDragActionUpEvent(event); 145 break; 146 } 147 return false; 148 } 149 handleDockWindowEvent(MotionEvent event)150 private boolean handleDockWindowEvent(MotionEvent event) { 151 switch (event.getActionMasked()) { 152 case MotionEvent.ACTION_DOWN: 153 handleDragActionDownEvent(event); 154 break; 155 case MotionEvent.ACTION_MOVE: 156 handleDragActionMoveEvent(event); 157 break; 158 case MotionEvent.ACTION_UP: 159 case MotionEvent.ACTION_CANCEL: 160 handleDragActionUpEvent(event); 161 break; 162 } 163 return true; 164 } 165 handleDragActionDownEvent(MotionEvent event)166 private void handleDragActionDownEvent(MotionEvent event) { 167 mVelocityTracker = VelocityTracker.obtain(); 168 mVelocityTracker.addMovement(event); 169 mDockWindowTouchSlopExceeded = false; 170 mTouchDownX = (int) event.getX(); 171 mTouchDownY = (int) event.getY(); 172 173 if (mNavigationBarView != null) { 174 View recentsButton = mNavigationBarView.getRecentsButton().getCurrentView(); 175 if (recentsButton != null) { 176 mDownOnRecents = mTouchDownX >= recentsButton.getLeft() 177 && mTouchDownX <= recentsButton.getRight() 178 && mTouchDownY >= recentsButton.getTop() 179 && mTouchDownY <= recentsButton.getBottom(); 180 } else { 181 mDownOnRecents = false; 182 } 183 } 184 } 185 handleDragActionMoveEvent(MotionEvent event)186 private boolean handleDragActionMoveEvent(MotionEvent event) { 187 mVelocityTracker.addMovement(event); 188 int x = (int) event.getX(); 189 int y = (int) event.getY(); 190 int xDiff = Math.abs(x - mTouchDownX); 191 int yDiff = Math.abs(y - mTouchDownY); 192 if (mDivider == null || mRecentsComponent == null) { 193 return false; 194 } 195 if (!mDockWindowTouchSlopExceeded) { 196 boolean touchSlopExceeded = !mIsVertical 197 ? yDiff > mScrollTouchSlop && yDiff > xDiff 198 : xDiff > mScrollTouchSlop && xDiff > yDiff; 199 if (mDownOnRecents && touchSlopExceeded 200 && mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) { 201 Rect initialBounds = null; 202 int dragMode = calculateDragMode(); 203 int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; 204 if (dragMode == DRAG_MODE_DIVIDER) { 205 initialBounds = new Rect(); 206 mDivider.getView().calculateBoundsForPosition(mIsVertical 207 ? (int) event.getRawX() 208 : (int) event.getRawY(), 209 mDivider.getView().isHorizontalDivision() 210 ? DOCKED_TOP 211 : DOCKED_LEFT, 212 initialBounds); 213 } else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX 214 < mContext.getResources().getDisplayMetrics().widthPixels / 2) { 215 createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; 216 } 217 boolean docked = mRecentsComponent.dockTopTask(dragMode, createMode, initialBounds, 218 MetricsEvent.ACTION_WINDOW_DOCK_SWIPE); 219 if (docked) { 220 mDragMode = dragMode; 221 if (mDragMode == DRAG_MODE_DIVIDER) { 222 mDivider.getView().startDragging(false /* animate */, true /* touching*/); 223 } 224 mDockWindowTouchSlopExceeded = true; 225 return true; 226 } 227 } 228 } else { 229 if (mDragMode == DRAG_MODE_DIVIDER) { 230 int position = !mIsVertical ? (int) event.getRawY() : (int) event.getRawX(); 231 SnapTarget snapTarget = mDivider.getView().getSnapAlgorithm() 232 .calculateSnapTarget(position, 0f /* velocity */, false /* hardDismiss */); 233 mDivider.getView().resizeStack(position, snapTarget.position, snapTarget); 234 } else if (mDragMode == DRAG_MODE_RECENTS) { 235 mRecentsComponent.onDraggingInRecents(event.getRawY()); 236 } 237 } 238 return false; 239 } 240 handleDragActionUpEvent(MotionEvent event)241 private void handleDragActionUpEvent(MotionEvent event) { 242 mVelocityTracker.addMovement(event); 243 mVelocityTracker.computeCurrentVelocity(1000); 244 if (mDockWindowTouchSlopExceeded && mDivider != null && mRecentsComponent != null) { 245 if (mDragMode == DRAG_MODE_DIVIDER) { 246 mDivider.getView().stopDragging(mIsVertical 247 ? (int) event.getRawX() 248 : (int) event.getRawY(), 249 mIsVertical 250 ? mVelocityTracker.getXVelocity() 251 : mVelocityTracker.getYVelocity(), 252 true /* avoidDismissStart */, false /* logMetrics */); 253 } else if (mDragMode == DRAG_MODE_RECENTS) { 254 mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity()); 255 } 256 } 257 mVelocityTracker.recycle(); 258 mVelocityTracker = null; 259 } 260 calculateDragMode()261 private int calculateDragMode() { 262 if (mIsVertical && !mDivider.getView().isHorizontalDivision()) { 263 return DRAG_MODE_DIVIDER; 264 } 265 if (!mIsVertical && mDivider.getView().isHorizontalDivision()) { 266 return DRAG_MODE_DIVIDER; 267 } 268 return DRAG_MODE_RECENTS; 269 } 270 onTouchEvent(MotionEvent event)271 public boolean onTouchEvent(MotionEvent event) { 272 boolean result = mTaskSwitcherDetector.onTouchEvent(event); 273 if (mDockWindowEnabled) { 274 result |= handleDockWindowEvent(event); 275 } 276 return result; 277 } 278 279 @Override onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)280 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 281 float absVelX = Math.abs(velocityX); 282 float absVelY = Math.abs(velocityY); 283 boolean isValidFling = absVelX > mMinFlingVelocity && 284 mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY); 285 if (isValidFling && mRecentsComponent != null) { 286 boolean showNext; 287 if (!mIsRTL) { 288 showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0); 289 } else { 290 // In RTL, vertical is still the same, but horizontal is flipped 291 showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0); 292 } 293 if (showNext) { 294 mRecentsComponent.showNextAffiliatedTask(); 295 } else { 296 mRecentsComponent.showPrevAffiliatedTask(); 297 } 298 } 299 return true; 300 } 301 302 @Override onTuningChanged(String key, String newValue)303 public void onTuningChanged(String key, String newValue) { 304 switch (key) { 305 case KEY_DOCK_WINDOW_GESTURE: 306 mDockWindowEnabled = newValue != null && (Integer.parseInt(newValue) != 0); 307 break; 308 } 309 } 310 } 311