1 /** 2 * Copyright (C) 2019 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 package com.android.systemui.statusbar.phone; 17 18 import static android.view.Display.INVALID_DISPLAY; 19 20 import android.app.ActivityManager; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.res.Resources; 26 import android.graphics.PixelFormat; 27 import android.graphics.Point; 28 import android.graphics.PointF; 29 import android.graphics.Region; 30 import android.hardware.display.DisplayManager; 31 import android.hardware.display.DisplayManager.DisplayListener; 32 import android.hardware.input.InputManager; 33 import android.os.Looper; 34 import android.os.RemoteException; 35 import android.os.SystemClock; 36 import android.os.SystemProperties; 37 import android.provider.DeviceConfig; 38 import android.util.DisplayMetrics; 39 import android.util.Log; 40 import android.util.TypedValue; 41 import android.view.ISystemGestureExclusionListener; 42 import android.view.InputChannel; 43 import android.view.InputDevice; 44 import android.view.InputEvent; 45 import android.view.InputEventReceiver; 46 import android.view.InputMonitor; 47 import android.view.KeyCharacterMap; 48 import android.view.KeyEvent; 49 import android.view.MotionEvent; 50 import android.view.Surface; 51 import android.view.ViewConfiguration; 52 import android.view.WindowManager; 53 import android.view.WindowManagerGlobal; 54 55 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 56 import com.android.internal.policy.GestureNavigationSettingsObserver; 57 import com.android.systemui.Dependency; 58 import com.android.systemui.R; 59 import com.android.systemui.broadcast.BroadcastDispatcher; 60 import com.android.systemui.bubbles.BubbleController; 61 import com.android.systemui.model.SysUiState; 62 import com.android.systemui.plugins.NavigationEdgeBackPlugin; 63 import com.android.systemui.plugins.PluginListener; 64 import com.android.systemui.recents.OverviewProxyService; 65 import com.android.systemui.settings.CurrentUserTracker; 66 import com.android.systemui.shared.plugins.PluginManager; 67 import com.android.systemui.shared.system.ActivityManagerWrapper; 68 import com.android.systemui.shared.system.QuickStepContract; 69 import com.android.systemui.shared.system.SysUiStatsLog; 70 import com.android.systemui.shared.system.TaskStackChangeListener; 71 import com.android.systemui.shared.tracing.ProtoTraceable; 72 import com.android.systemui.tracing.ProtoTracer; 73 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; 74 import com.android.systemui.tracing.nano.SystemUiTraceProto; 75 76 import java.io.PrintWriter; 77 import java.util.ArrayList; 78 import java.util.List; 79 import java.util.concurrent.Executor; 80 81 /** 82 * Utility class to handle edge swipes for back gesture 83 */ 84 public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener, 85 PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { 86 87 private static final String TAG = "EdgeBackGestureHandler"; 88 private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( 89 "gestures.back_timeout", 250); 90 91 private ISystemGestureExclusionListener mGestureExclusionListener = 92 new ISystemGestureExclusionListener.Stub() { 93 @Override 94 public void onSystemGestureExclusionChanged(int displayId, 95 Region systemGestureExclusion, Region unrestrictedOrNull) { 96 if (displayId == mDisplayId) { 97 mMainExecutor.execute(() -> { 98 mExcludeRegion.set(systemGestureExclusion); 99 mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null 100 ? unrestrictedOrNull : systemGestureExclusion); 101 }); 102 } 103 } 104 }; 105 106 private OverviewProxyService.OverviewProxyListener mQuickSwitchListener = 107 new OverviewProxyService.OverviewProxyListener() { 108 @Override 109 public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { 110 mStartingQuickstepRotation = rotation; 111 updateDisabledForQuickstep(); 112 } 113 }; 114 115 private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 116 @Override 117 public void onTaskStackChanged() { 118 mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); 119 } 120 }; 121 122 private final Context mContext; 123 private final OverviewProxyService mOverviewProxyService; 124 private final Runnable mStateChangeCallback; 125 126 private final PluginManager mPluginManager; 127 // Activities which should not trigger Back gesture. 128 private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>(); 129 130 private final Point mDisplaySize = new Point(); 131 private final int mDisplayId; 132 133 private final Executor mMainExecutor; 134 135 private final Region mExcludeRegion = new Region(); 136 private final Region mUnrestrictedExcludeRegion = new Region(); 137 138 // The left side edge width where touch down is allowed 139 private int mEdgeWidthLeft; 140 // The right side edge width where touch down is allowed 141 private int mEdgeWidthRight; 142 // The bottom gesture area height 143 private float mBottomGestureHeight; 144 // The slop to distinguish between horizontal and vertical motion 145 private float mTouchSlop; 146 // Duration after which we consider the event as longpress. 147 private final int mLongPressTimeout; 148 private int mStartingQuickstepRotation = -1; 149 // We temporarily disable back gesture when user is quickswitching 150 // between apps of different orientations 151 private boolean mDisabledForQuickstep; 152 153 private final PointF mDownPoint = new PointF(); 154 private final PointF mEndPoint = new PointF(); 155 private boolean mThresholdCrossed = false; 156 private boolean mAllowGesture = false; 157 private boolean mLogGesture = false; 158 private boolean mInRejectedExclusion = false; 159 private boolean mIsOnLeftEdge; 160 161 private boolean mIsAttached; 162 private boolean mIsGesturalModeEnabled; 163 private boolean mIsEnabled; 164 private boolean mIsNavBarShownTransiently; 165 private boolean mIsBackGestureAllowed; 166 private boolean mGestureBlockingActivityRunning; 167 168 private InputMonitor mInputMonitor; 169 private InputEventReceiver mInputEventReceiver; 170 171 private NavigationEdgeBackPlugin mEdgeBackPlugin; 172 private int mLeftInset; 173 private int mRightInset; 174 private int mSysUiFlags; 175 176 private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; 177 178 private final NavigationEdgeBackPlugin.BackCallback mBackCallback = 179 new NavigationEdgeBackPlugin.BackCallback() { 180 @Override 181 public void triggerBack() { 182 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); 183 sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); 184 185 mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, 186 (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); 187 logGesture(mInRejectedExclusion 188 ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED 189 : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED); 190 } 191 192 @Override 193 public void cancelBack() { 194 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE); 195 mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x, 196 (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); 197 } 198 }; 199 EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiFlagContainer, PluginManager pluginManager, Runnable stateChangeCallback)200 public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, 201 SysUiState sysUiFlagContainer, PluginManager pluginManager, 202 Runnable stateChangeCallback) { 203 super(Dependency.get(BroadcastDispatcher.class)); 204 mContext = context; 205 mDisplayId = context.getDisplayId(); 206 mMainExecutor = context.getMainExecutor(); 207 mOverviewProxyService = overviewProxyService; 208 mPluginManager = pluginManager; 209 mStateChangeCallback = stateChangeCallback; 210 ComponentName recentsComponentName = ComponentName.unflattenFromString( 211 context.getString(com.android.internal.R.string.config_recentsComponentName)); 212 if (recentsComponentName != null) { 213 String recentsPackageName = recentsComponentName.getPackageName(); 214 PackageManager manager = context.getPackageManager(); 215 try { 216 Resources resources = manager.getResourcesForApplication(recentsPackageName); 217 int resId = resources.getIdentifier( 218 "gesture_blocking_activities", "array", recentsPackageName); 219 220 if (resId == 0) { 221 Log.e(TAG, "No resource found for gesture-blocking activities"); 222 } else { 223 String[] gestureBlockingActivities = resources.getStringArray(resId); 224 for (String gestureBlockingActivity : gestureBlockingActivities) { 225 mGestureBlockingActivities.add( 226 ComponentName.unflattenFromString(gestureBlockingActivity)); 227 } 228 } 229 } catch (NameNotFoundException e) { 230 Log.e(TAG, "Failed to add gesture blocking activities", e); 231 } 232 } 233 234 Dependency.get(ProtoTracer.class).add(this); 235 mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, 236 ViewConfiguration.getLongPressTimeout()); 237 238 mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( 239 mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged); 240 241 updateCurrentUserResources(); 242 sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags); 243 } 244 updateCurrentUserResources()245 public void updateCurrentUserResources() { 246 Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext() 247 .getResources(); 248 mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); 249 mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); 250 mIsBackGestureAllowed = 251 !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); 252 253 final DisplayMetrics dm = res.getDisplayMetrics(); 254 final float defaultGestureHeight = res.getDimension( 255 com.android.internal.R.dimen.navigation_bar_gesture_height) / dm.density; 256 final float gestureHeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 257 SystemUiDeviceConfigFlags.BACK_GESTURE_BOTTOM_HEIGHT, 258 defaultGestureHeight); 259 mBottomGestureHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, gestureHeight, 260 dm); 261 262 // Reduce the default touch slop to ensure that we can intercept the gesture 263 // before the app starts to react to it. 264 // TODO(b/130352502) Tune this value and extract into a constant 265 final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 266 SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f); 267 mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop() * backGestureSlop; 268 } 269 onNavigationSettingsChanged()270 private void onNavigationSettingsChanged() { 271 boolean wasBackAllowed = isHandlingGestures(); 272 updateCurrentUserResources(); 273 if (wasBackAllowed != isHandlingGestures()) { 274 mStateChangeCallback.run(); 275 } 276 } 277 278 @Override onUserSwitched(int newUserId)279 public void onUserSwitched(int newUserId) { 280 updateIsEnabled(); 281 updateCurrentUserResources(); 282 } 283 284 /** 285 * @see NavigationBarView#onAttachedToWindow() 286 */ onNavBarAttached()287 public void onNavBarAttached() { 288 mIsAttached = true; 289 mOverviewProxyService.addCallback(mQuickSwitchListener); 290 updateIsEnabled(); 291 startTracking(); 292 } 293 294 /** 295 * @see NavigationBarView#onDetachedFromWindow() 296 */ onNavBarDetached()297 public void onNavBarDetached() { 298 mIsAttached = false; 299 mOverviewProxyService.removeCallback(mQuickSwitchListener); 300 updateIsEnabled(); 301 stopTracking(); 302 } 303 304 /** 305 * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged 306 */ onNavigationModeChanged(int mode)307 public void onNavigationModeChanged(int mode) { 308 mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode); 309 updateIsEnabled(); 310 updateCurrentUserResources(); 311 } 312 onNavBarTransientStateChanged(boolean isTransient)313 public void onNavBarTransientStateChanged(boolean isTransient) { 314 mIsNavBarShownTransiently = isTransient; 315 } 316 disposeInputChannel()317 private void disposeInputChannel() { 318 if (mInputEventReceiver != null) { 319 mInputEventReceiver.dispose(); 320 mInputEventReceiver = null; 321 } 322 if (mInputMonitor != null) { 323 mInputMonitor.dispose(); 324 mInputMonitor = null; 325 } 326 } 327 updateIsEnabled()328 private void updateIsEnabled() { 329 boolean isEnabled = mIsAttached && mIsGesturalModeEnabled; 330 if (isEnabled == mIsEnabled) { 331 return; 332 } 333 mIsEnabled = isEnabled; 334 disposeInputChannel(); 335 336 if (mEdgeBackPlugin != null) { 337 mEdgeBackPlugin.onDestroy(); 338 mEdgeBackPlugin = null; 339 } 340 341 if (!mIsEnabled) { 342 mGestureNavigationSettingsObserver.unregister(); 343 mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); 344 mPluginManager.removePluginListener(this); 345 ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); 346 347 try { 348 WindowManagerGlobal.getWindowManagerService() 349 .unregisterSystemGestureExclusionListener( 350 mGestureExclusionListener, mDisplayId); 351 } catch (RemoteException | IllegalArgumentException e) { 352 Log.e(TAG, "Failed to unregister window manager callbacks", e); 353 } 354 355 } else { 356 mGestureNavigationSettingsObserver.register(); 357 updateDisplaySize(); 358 mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, 359 mContext.getMainThreadHandler()); 360 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); 361 362 try { 363 WindowManagerGlobal.getWindowManagerService() 364 .registerSystemGestureExclusionListener( 365 mGestureExclusionListener, mDisplayId); 366 } catch (RemoteException | IllegalArgumentException e) { 367 Log.e(TAG, "Failed to register window manager callbacks", e); 368 } 369 370 // Register input event receiver 371 mInputMonitor = InputManager.getInstance().monitorGestureInput( 372 "edge-swipe", mDisplayId); 373 mInputEventReceiver = new SysUiInputEventReceiver( 374 mInputMonitor.getInputChannel(), Looper.getMainLooper()); 375 376 // Add a nav bar panel window 377 setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); 378 mPluginManager.addPluginListener( 379 this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); 380 } 381 } 382 383 @Override onPluginConnected(NavigationEdgeBackPlugin plugin, Context context)384 public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) { 385 setEdgeBackPlugin(plugin); 386 } 387 388 @Override onPluginDisconnected(NavigationEdgeBackPlugin plugin)389 public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { 390 setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); 391 } 392 setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin)393 private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { 394 if (mEdgeBackPlugin != null) { 395 mEdgeBackPlugin.onDestroy(); 396 } 397 mEdgeBackPlugin = edgeBackPlugin; 398 mEdgeBackPlugin.setBackCallback(mBackCallback); 399 mEdgeBackPlugin.setLayoutParams(createLayoutParams()); 400 updateDisplaySize(); 401 } 402 isHandlingGestures()403 public boolean isHandlingGestures() { 404 return mIsEnabled && mIsBackGestureAllowed; 405 } 406 createLayoutParams()407 private WindowManager.LayoutParams createLayoutParams() { 408 Resources resources = mContext.getResources(); 409 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( 410 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width), 411 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height), 412 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 413 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 414 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 415 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 416 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, 417 PixelFormat.TRANSLUCENT); 418 layoutParams.privateFlags |= 419 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; 420 layoutParams.setTitle(TAG + mContext.getDisplayId()); 421 layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel); 422 layoutParams.windowAnimations = 0; 423 layoutParams.setFitInsetsTypes(0 /* types */); 424 return layoutParams; 425 } 426 onInputEvent(InputEvent ev)427 private void onInputEvent(InputEvent ev) { 428 if (ev instanceof MotionEvent) { 429 onMotionEvent((MotionEvent) ev); 430 } 431 } 432 isWithinTouchRegion(int x, int y)433 private boolean isWithinTouchRegion(int x, int y) { 434 // Disallow if we are in the bottom gesture area 435 if (y >= (mDisplaySize.y - mBottomGestureHeight)) { 436 return false; 437 } 438 439 // If the point is way too far (twice the margin), it is 440 // not interesting to us for logging purposes, nor we 441 // should process it. Simply return false and keep 442 // mLogGesture = false. 443 if (x > 2 * (mEdgeWidthLeft + mLeftInset) 444 && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) { 445 return false; 446 } 447 448 // Denotes whether we should proceed with the gesture. 449 // Even if it is false, we may want to log it assuming 450 // it is not invalid due to exclusion. 451 boolean withinRange = x <= mEdgeWidthLeft + mLeftInset 452 || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset); 453 454 // Always allow if the user is in a transient sticky immersive state 455 if (mIsNavBarShownTransiently) { 456 mLogGesture = true; 457 return withinRange; 458 } 459 460 if (mExcludeRegion.contains(x, y)) { 461 if (withinRange) { 462 // Log as exclusion only if it is in acceptable range in the first place. 463 mOverviewProxyService.notifyBackAction( 464 false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge); 465 // We don't have the end point for logging purposes. 466 mEndPoint.x = -1; 467 mEndPoint.y = -1; 468 mLogGesture = true; 469 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED); 470 } 471 return false; 472 } 473 474 mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y); 475 mLogGesture = true; 476 return withinRange; 477 } 478 cancelGesture(MotionEvent ev)479 private void cancelGesture(MotionEvent ev) { 480 // Send action cancel to reset all the touch events 481 mAllowGesture = false; 482 mLogGesture = false; 483 mInRejectedExclusion = false; 484 MotionEvent cancelEv = MotionEvent.obtain(ev); 485 cancelEv.setAction(MotionEvent.ACTION_CANCEL); 486 mEdgeBackPlugin.onMotionEvent(cancelEv); 487 cancelEv.recycle(); 488 } 489 logGesture(int backType)490 private void logGesture(int backType) { 491 if (!mLogGesture) { 492 return; 493 } 494 mLogGesture = false; 495 SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType, 496 (int) mDownPoint.y, mIsOnLeftEdge 497 ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT 498 : SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT, 499 (int) mDownPoint.x, (int) mDownPoint.y, 500 (int) mEndPoint.x, (int) mEndPoint.y, 501 mEdgeWidthLeft + mLeftInset, 502 mDisplaySize.x - (mEdgeWidthRight + mRightInset)); 503 } 504 onMotionEvent(MotionEvent ev)505 private void onMotionEvent(MotionEvent ev) { 506 int action = ev.getActionMasked(); 507 if (action == MotionEvent.ACTION_DOWN) { 508 // Verify if this is in within the touch region and we aren't in immersive mode, and 509 // either the bouncer is showing or the notification panel is hidden 510 mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; 511 mLogGesture = false; 512 mInRejectedExclusion = false; 513 mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed 514 && !mGestureBlockingActivityRunning 515 && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) 516 && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); 517 if (mAllowGesture) { 518 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); 519 mEdgeBackPlugin.onMotionEvent(ev); 520 } 521 if (mLogGesture) { 522 mDownPoint.set(ev.getX(), ev.getY()); 523 mEndPoint.set(-1, -1); 524 mThresholdCrossed = false; 525 } 526 } else if (mAllowGesture || mLogGesture) { 527 if (!mThresholdCrossed) { 528 mEndPoint.x = (int) ev.getX(); 529 mEndPoint.y = (int) ev.getY(); 530 if (action == MotionEvent.ACTION_POINTER_DOWN) { 531 if (mAllowGesture) { 532 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH); 533 // We do not support multi touch for back gesture 534 cancelGesture(ev); 535 } 536 mLogGesture = false; 537 return; 538 } else if (action == MotionEvent.ACTION_MOVE) { 539 if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) { 540 if (mAllowGesture) { 541 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS); 542 cancelGesture(ev); 543 } 544 mLogGesture = false; 545 return; 546 } 547 float dx = Math.abs(ev.getX() - mDownPoint.x); 548 float dy = Math.abs(ev.getY() - mDownPoint.y); 549 if (dy > dx && dy > mTouchSlop) { 550 if (mAllowGesture) { 551 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE); 552 cancelGesture(ev); 553 } 554 mLogGesture = false; 555 return; 556 } else if (dx > dy && dx > mTouchSlop) { 557 if (mAllowGesture) { 558 mThresholdCrossed = true; 559 // Capture inputs 560 mInputMonitor.pilferPointers(); 561 } else { 562 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE); 563 } 564 } 565 } 566 } 567 568 if (mAllowGesture) { 569 // forward touch 570 mEdgeBackPlugin.onMotionEvent(ev); 571 } 572 } 573 574 Dependency.get(ProtoTracer.class).update(); 575 } 576 updateDisabledForQuickstep()577 private void updateDisabledForQuickstep() { 578 int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation(); 579 mDisabledForQuickstep = mStartingQuickstepRotation > -1 && 580 mStartingQuickstepRotation != rotation; 581 } 582 583 @Override onDisplayAdded(int displayId)584 public void onDisplayAdded(int displayId) { } 585 586 @Override onDisplayRemoved(int displayId)587 public void onDisplayRemoved(int displayId) { } 588 589 @Override onDisplayChanged(int displayId)590 public void onDisplayChanged(int displayId) { 591 if (mStartingQuickstepRotation > -1) { 592 updateDisabledForQuickstep(); 593 } 594 595 if (displayId == mDisplayId) { 596 updateDisplaySize(); 597 } 598 } 599 updateDisplaySize()600 private void updateDisplaySize() { 601 mContext.getDisplay().getRealSize(mDisplaySize); 602 if (mEdgeBackPlugin != null) { 603 mEdgeBackPlugin.setDisplaySize(mDisplaySize); 604 } 605 } 606 sendEvent(int action, int code)607 private void sendEvent(int action, int code) { 608 long when = SystemClock.uptimeMillis(); 609 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 610 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 611 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 612 InputDevice.SOURCE_KEYBOARD); 613 614 // Bubble controller will give us a valid display id if it should get the back event 615 BubbleController bubbleController = Dependency.get(BubbleController.class); 616 int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext); 617 if (code == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) { 618 ev.setDisplayId(bubbleDisplayId); 619 } 620 InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 621 } 622 setInsets(int leftInset, int rightInset)623 public void setInsets(int leftInset, int rightInset) { 624 mLeftInset = leftInset; 625 mRightInset = rightInset; 626 if (mEdgeBackPlugin != null) { 627 mEdgeBackPlugin.setInsets(leftInset, rightInset); 628 } 629 } 630 dump(PrintWriter pw)631 public void dump(PrintWriter pw) { 632 pw.println("EdgeBackGestureHandler:"); 633 pw.println(" mIsEnabled=" + mIsEnabled); 634 pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); 635 pw.println(" mAllowGesture=" + mAllowGesture); 636 pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); 637 pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); 638 pw.println(" mInRejectedExclusion" + mInRejectedExclusion); 639 pw.println(" mExcludeRegion=" + mExcludeRegion); 640 pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); 641 pw.println(" mIsAttached=" + mIsAttached); 642 pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); 643 pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); 644 } 645 isGestureBlockingActivityRunning()646 private boolean isGestureBlockingActivityRunning() { 647 ActivityManager.RunningTaskInfo runningTask = 648 ActivityManagerWrapper.getInstance().getRunningTask(); 649 ComponentName topActivity = runningTask == null ? null : runningTask.topActivity; 650 return topActivity != null && mGestureBlockingActivities.contains(topActivity); 651 } 652 653 @Override writeToProto(SystemUiTraceProto proto)654 public void writeToProto(SystemUiTraceProto proto) { 655 if (proto.edgeBackGestureHandler == null) { 656 proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto(); 657 } 658 proto.edgeBackGestureHandler.allowGesture = mAllowGesture; 659 } 660 661 class SysUiInputEventReceiver extends InputEventReceiver { SysUiInputEventReceiver(InputChannel channel, Looper looper)662 SysUiInputEventReceiver(InputChannel channel, Looper looper) { 663 super(channel, looper); 664 } 665 onInputEvent(InputEvent event)666 public void onInputEvent(InputEvent event) { 667 EdgeBackGestureHandler.this.onInputEvent(event); 668 finishInputEvent(event, true); 669 } 670 } 671 } 672