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