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