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.SystemUIFactory; 60 import com.android.systemui.broadcast.BroadcastDispatcher; 61 import com.android.systemui.bubbles.BubbleController; 62 import com.android.systemui.model.SysUiState; 63 import com.android.systemui.plugins.NavigationEdgeBackPlugin; 64 import com.android.systemui.plugins.PluginListener; 65 import com.android.systemui.recents.OverviewProxyService; 66 import com.android.systemui.settings.CurrentUserTracker; 67 import com.android.systemui.shared.plugins.PluginManager; 68 import com.android.systemui.shared.system.ActivityManagerWrapper; 69 import com.android.systemui.shared.system.QuickStepContract; 70 import com.android.systemui.shared.system.SysUiStatsLog; 71 import com.android.systemui.shared.system.TaskStackChangeListener; 72 import com.android.systemui.shared.tracing.ProtoTraceable; 73 import com.android.systemui.tracing.ProtoTracer; 74 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; 75 import com.android.systemui.tracing.nano.SystemUiTraceProto; 76 77 import java.io.PrintWriter; 78 import java.util.ArrayDeque; 79 import java.util.ArrayList; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.concurrent.Executor; 83 84 /** 85 * Utility class to handle edge swipes for back gesture 86 */ 87 public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener, 88 PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { 89 90 private static final String TAG = "EdgeBackGestureHandler"; 91 private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( 92 "gestures.back_timeout", 250); 93 94 private ISystemGestureExclusionListener mGestureExclusionListener = 95 new ISystemGestureExclusionListener.Stub() { 96 @Override 97 public void onSystemGestureExclusionChanged(int displayId, 98 Region systemGestureExclusion, Region unrestrictedOrNull) { 99 if (displayId == mDisplayId) { 100 mMainExecutor.execute(() -> { 101 mExcludeRegion.set(systemGestureExclusion); 102 mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null 103 ? unrestrictedOrNull : systemGestureExclusion); 104 }); 105 } 106 } 107 }; 108 109 private OverviewProxyService.OverviewProxyListener mQuickSwitchListener = 110 new OverviewProxyService.OverviewProxyListener() { 111 @Override 112 public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) { 113 mStartingQuickstepRotation = rotation; 114 updateDisabledForQuickstep(); 115 } 116 }; 117 118 private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 119 @Override 120 public void onTaskStackChanged() { 121 mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); 122 } 123 @Override 124 public void onTaskCreated(int taskId, ComponentName componentName) { 125 if (componentName != null) { 126 mPackageName = componentName.getPackageName(); 127 } else { 128 mPackageName = "_UNKNOWN"; 129 } 130 } 131 }; 132 133 private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = 134 new DeviceConfig.OnPropertiesChangedListener() { 135 @Override 136 public void onPropertiesChanged(DeviceConfig.Properties properties) { 137 if (DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace()) 138 && (properties.getKeyset().contains( 139 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD) 140 || properties.getKeyset().contains( 141 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL) 142 || properties.getKeyset().contains( 143 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME))) { 144 updateMLModelState(); 145 } 146 } 147 }; 148 149 150 private final Context mContext; 151 private final OverviewProxyService mOverviewProxyService; 152 private final Runnable mStateChangeCallback; 153 154 private final PluginManager mPluginManager; 155 // Activities which should not trigger Back gesture. 156 private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>(); 157 158 private final Point mDisplaySize = new Point(); 159 private final int mDisplayId; 160 161 private final Executor mMainExecutor; 162 163 private final Region mExcludeRegion = new Region(); 164 private final Region mUnrestrictedExcludeRegion = new Region(); 165 166 // The left side edge width where touch down is allowed 167 private int mEdgeWidthLeft; 168 // The right side edge width where touch down is allowed 169 private int mEdgeWidthRight; 170 // The bottom gesture area height 171 private float mBottomGestureHeight; 172 // The slop to distinguish between horizontal and vertical motion 173 private float mTouchSlop; 174 // Duration after which we consider the event as longpress. 175 private final int mLongPressTimeout; 176 private int mStartingQuickstepRotation = -1; 177 // We temporarily disable back gesture when user is quickswitching 178 // between apps of different orientations 179 private boolean mDisabledForQuickstep; 180 181 private final PointF mDownPoint = new PointF(); 182 private final PointF mEndPoint = new PointF(); 183 private boolean mThresholdCrossed = false; 184 private boolean mAllowGesture = false; 185 private boolean mLogGesture = false; 186 private boolean mInRejectedExclusion = false; 187 private boolean mIsOnLeftEdge; 188 189 private boolean mIsAttached; 190 private boolean mIsGesturalModeEnabled; 191 private boolean mIsEnabled; 192 private boolean mIsNavBarShownTransiently; 193 private boolean mIsBackGestureAllowed; 194 private boolean mGestureBlockingActivityRunning; 195 196 private InputMonitor mInputMonitor; 197 private InputEventReceiver mInputEventReceiver; 198 199 private NavigationEdgeBackPlugin mEdgeBackPlugin; 200 private int mLeftInset; 201 private int mRightInset; 202 private int mSysUiFlags; 203 204 // For Tf-Lite model. 205 private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider; 206 private Map<String, Integer> mVocab; 207 private boolean mUseMLModel; 208 // minimum width below which we do not run the model 209 private int mMLEnableWidth; 210 private float mMLModelThreshold; 211 private String mPackageName; 212 private float mMLResults; 213 214 private static final int MAX_LOGGED_PREDICTIONS = 10; 215 private ArrayDeque<String> mPredictionLog = new ArrayDeque<>(); 216 217 private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; 218 219 private final NavigationEdgeBackPlugin.BackCallback mBackCallback = 220 new NavigationEdgeBackPlugin.BackCallback() { 221 @Override 222 public void triggerBack() { 223 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); 224 sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); 225 226 mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, 227 (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); 228 logGesture(mInRejectedExclusion 229 ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED 230 : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED); 231 } 232 233 @Override 234 public void cancelBack() { 235 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE); 236 mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x, 237 (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); 238 } 239 }; 240 EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiFlagContainer, PluginManager pluginManager, Runnable stateChangeCallback)241 public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, 242 SysUiState sysUiFlagContainer, PluginManager pluginManager, 243 Runnable stateChangeCallback) { 244 super(Dependency.get(BroadcastDispatcher.class)); 245 mContext = context; 246 mDisplayId = context.getDisplayId(); 247 mMainExecutor = context.getMainExecutor(); 248 mOverviewProxyService = overviewProxyService; 249 mPluginManager = pluginManager; 250 mStateChangeCallback = stateChangeCallback; 251 ComponentName recentsComponentName = ComponentName.unflattenFromString( 252 context.getString(com.android.internal.R.string.config_recentsComponentName)); 253 if (recentsComponentName != null) { 254 String recentsPackageName = recentsComponentName.getPackageName(); 255 PackageManager manager = context.getPackageManager(); 256 try { 257 Resources resources = manager.getResourcesForApplication(recentsPackageName); 258 int resId = resources.getIdentifier( 259 "gesture_blocking_activities", "array", recentsPackageName); 260 261 if (resId == 0) { 262 Log.e(TAG, "No resource found for gesture-blocking activities"); 263 } else { 264 String[] gestureBlockingActivities = resources.getStringArray(resId); 265 for (String gestureBlockingActivity : gestureBlockingActivities) { 266 mGestureBlockingActivities.add( 267 ComponentName.unflattenFromString(gestureBlockingActivity)); 268 } 269 } 270 } catch (NameNotFoundException e) { 271 Log.e(TAG, "Failed to add gesture blocking activities", e); 272 } 273 } 274 mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, 275 ViewConfiguration.getLongPressTimeout()); 276 277 mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( 278 mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged); 279 280 updateCurrentUserResources(); 281 sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags); 282 } 283 updateCurrentUserResources()284 public void updateCurrentUserResources() { 285 Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext() 286 .getResources(); 287 mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); 288 mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); 289 mIsBackGestureAllowed = 290 !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); 291 292 final DisplayMetrics dm = res.getDisplayMetrics(); 293 final float defaultGestureHeight = res.getDimension( 294 com.android.internal.R.dimen.navigation_bar_gesture_height) / dm.density; 295 final float gestureHeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 296 SystemUiDeviceConfigFlags.BACK_GESTURE_BOTTOM_HEIGHT, 297 defaultGestureHeight); 298 mBottomGestureHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, gestureHeight, 299 dm); 300 301 // Set the minimum bounds to activate ML to 12dp or the minimum of configured values 302 mMLEnableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12.0f, dm); 303 if (mMLEnableWidth > mEdgeWidthRight) mMLEnableWidth = mEdgeWidthRight; 304 if (mMLEnableWidth > mEdgeWidthLeft) mMLEnableWidth = mEdgeWidthLeft; 305 306 // Reduce the default touch slop to ensure that we can intercept the gesture 307 // before the app starts to react to it. 308 // TODO(b/130352502) Tune this value and extract into a constant 309 final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 310 SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f); 311 mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop() * backGestureSlop; 312 } 313 onNavigationSettingsChanged()314 private void onNavigationSettingsChanged() { 315 boolean wasBackAllowed = isHandlingGestures(); 316 updateCurrentUserResources(); 317 if (wasBackAllowed != isHandlingGestures()) { 318 mStateChangeCallback.run(); 319 } 320 } 321 322 @Override onUserSwitched(int newUserId)323 public void onUserSwitched(int newUserId) { 324 updateIsEnabled(); 325 updateCurrentUserResources(); 326 } 327 328 /** 329 * @see NavigationBarView#onAttachedToWindow() 330 */ onNavBarAttached()331 public void onNavBarAttached() { 332 mIsAttached = true; 333 Dependency.get(ProtoTracer.class).add(this); 334 mOverviewProxyService.addCallback(mQuickSwitchListener); 335 updateIsEnabled(); 336 startTracking(); 337 } 338 339 /** 340 * @see NavigationBarView#onDetachedFromWindow() 341 */ onNavBarDetached()342 public void onNavBarDetached() { 343 mIsAttached = false; 344 Dependency.get(ProtoTracer.class).remove(this); 345 mOverviewProxyService.removeCallback(mQuickSwitchListener); 346 updateIsEnabled(); 347 stopTracking(); 348 } 349 350 /** 351 * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged 352 */ onNavigationModeChanged(int mode)353 public void onNavigationModeChanged(int mode) { 354 mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode); 355 updateIsEnabled(); 356 updateCurrentUserResources(); 357 } 358 onNavBarTransientStateChanged(boolean isTransient)359 public void onNavBarTransientStateChanged(boolean isTransient) { 360 mIsNavBarShownTransiently = isTransient; 361 } 362 disposeInputChannel()363 private void disposeInputChannel() { 364 if (mInputEventReceiver != null) { 365 mInputEventReceiver.dispose(); 366 mInputEventReceiver = null; 367 } 368 if (mInputMonitor != null) { 369 mInputMonitor.dispose(); 370 mInputMonitor = null; 371 } 372 } 373 updateIsEnabled()374 private void updateIsEnabled() { 375 boolean isEnabled = mIsAttached && mIsGesturalModeEnabled; 376 if (isEnabled == mIsEnabled) { 377 return; 378 } 379 mIsEnabled = isEnabled; 380 disposeInputChannel(); 381 382 if (mEdgeBackPlugin != null) { 383 mEdgeBackPlugin.onDestroy(); 384 mEdgeBackPlugin = null; 385 } 386 387 if (!mIsEnabled) { 388 mGestureNavigationSettingsObserver.unregister(); 389 mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); 390 mPluginManager.removePluginListener(this); 391 ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); 392 DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); 393 394 try { 395 WindowManagerGlobal.getWindowManagerService() 396 .unregisterSystemGestureExclusionListener( 397 mGestureExclusionListener, mDisplayId); 398 } catch (RemoteException | IllegalArgumentException e) { 399 Log.e(TAG, "Failed to unregister window manager callbacks", e); 400 } 401 402 } else { 403 mGestureNavigationSettingsObserver.register(); 404 updateDisplaySize(); 405 mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, 406 mContext.getMainThreadHandler()); 407 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); 408 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, 409 runnable -> (mContext.getMainThreadHandler()).post(runnable), 410 mOnPropertiesChangedListener); 411 412 try { 413 WindowManagerGlobal.getWindowManagerService() 414 .registerSystemGestureExclusionListener( 415 mGestureExclusionListener, mDisplayId); 416 } catch (RemoteException | IllegalArgumentException e) { 417 Log.e(TAG, "Failed to register window manager callbacks", e); 418 } 419 420 // Register input event receiver 421 mInputMonitor = InputManager.getInstance().monitorGestureInput( 422 "edge-swipe", mDisplayId); 423 mInputEventReceiver = new SysUiInputEventReceiver( 424 mInputMonitor.getInputChannel(), Looper.getMainLooper()); 425 426 // Add a nav bar panel window 427 setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); 428 mPluginManager.addPluginListener( 429 this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); 430 } 431 // Update the ML model resources. 432 updateMLModelState(); 433 } 434 435 @Override onPluginConnected(NavigationEdgeBackPlugin plugin, Context context)436 public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) { 437 setEdgeBackPlugin(plugin); 438 } 439 440 @Override onPluginDisconnected(NavigationEdgeBackPlugin plugin)441 public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { 442 setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); 443 } 444 setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin)445 private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { 446 if (mEdgeBackPlugin != null) { 447 mEdgeBackPlugin.onDestroy(); 448 } 449 mEdgeBackPlugin = edgeBackPlugin; 450 mEdgeBackPlugin.setBackCallback(mBackCallback); 451 mEdgeBackPlugin.setLayoutParams(createLayoutParams()); 452 updateDisplaySize(); 453 } 454 isHandlingGestures()455 public boolean isHandlingGestures() { 456 return mIsEnabled && mIsBackGestureAllowed; 457 } 458 createLayoutParams()459 private WindowManager.LayoutParams createLayoutParams() { 460 Resources resources = mContext.getResources(); 461 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( 462 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width), 463 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height), 464 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 465 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 466 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 467 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 468 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, 469 PixelFormat.TRANSLUCENT); 470 layoutParams.privateFlags |= 471 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; 472 layoutParams.setTitle(TAG + mContext.getDisplayId()); 473 layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel); 474 layoutParams.windowAnimations = 0; 475 layoutParams.setFitInsetsTypes(0 /* types */); 476 return layoutParams; 477 } 478 onInputEvent(InputEvent ev)479 private void onInputEvent(InputEvent ev) { 480 if (ev instanceof MotionEvent) { 481 onMotionEvent((MotionEvent) ev); 482 } 483 } 484 updateMLModelState()485 private void updateMLModelState() { 486 boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, 487 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false); 488 489 if (newState == mUseMLModel) { 490 return; 491 } 492 493 if (newState) { 494 String mlModelName = DeviceConfig.getString(DeviceConfig.NAMESPACE_SYSTEMUI, 495 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME, "backgesture"); 496 mBackGestureTfClassifierProvider = SystemUIFactory.getInstance() 497 .createBackGestureTfClassifierProvider(mContext.getAssets(), mlModelName); 498 mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 499 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f); 500 if (mBackGestureTfClassifierProvider.isActive()) { 501 mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets()); 502 mUseMLModel = true; 503 return; 504 } 505 } 506 507 mUseMLModel = false; 508 if (mBackGestureTfClassifierProvider != null) { 509 mBackGestureTfClassifierProvider.release(); 510 mBackGestureTfClassifierProvider = null; 511 } 512 } 513 getBackGesturePredictionsCategory(int x, int y, int app)514 private int getBackGesturePredictionsCategory(int x, int y, int app) { 515 if (app == -1) { 516 return -1; 517 } 518 519 int distanceFromEdge; 520 int location; 521 if (x <= mDisplaySize.x / 2.0) { 522 location = 1; // left 523 distanceFromEdge = x; 524 } else { 525 location = 2; // right 526 distanceFromEdge = mDisplaySize.x - x; 527 } 528 529 Object[] featuresVector = { 530 new long[]{(long) mDisplaySize.x}, 531 new long[]{(long) distanceFromEdge}, 532 new long[]{(long) location}, 533 new long[]{(long) app}, 534 new long[]{(long) y}, 535 }; 536 537 mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector); 538 if (mMLResults == -1) { 539 return -1; 540 } 541 542 return mMLResults >= mMLModelThreshold ? 1 : 0; 543 } 544 isWithinTouchRegion(int x, int y)545 private boolean isWithinTouchRegion(int x, int y) { 546 // Disallow if we are in the bottom gesture area 547 if (y >= (mDisplaySize.y - mBottomGestureHeight)) { 548 return false; 549 } 550 // If the point is way too far (twice the margin), it is 551 // not interesting to us for logging purposes, nor we 552 // should process it. Simply return false and keep 553 // mLogGesture = false. 554 if (x > 2 * (mEdgeWidthLeft + mLeftInset) 555 && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) { 556 return false; 557 } 558 559 int app = -1; 560 if (mVocab != null) { 561 app = mVocab.getOrDefault(mPackageName, -1); 562 } 563 564 // Denotes whether we should proceed with the gesture. Even if it is false, we may want to 565 // log it assuming it is not invalid due to exclusion. 566 boolean withinRange = x < mEdgeWidthLeft + mLeftInset 567 || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset); 568 if (withinRange) { 569 int results = -1; 570 571 // Check if we are within the tightest bounds beyond which we would not need to run the 572 // ML model 573 boolean withinMinRange = x < mMLEnableWidth + mLeftInset 574 || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset); 575 if (!withinMinRange && mUseMLModel 576 && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) { 577 withinRange = (results == 1); 578 } 579 } 580 581 // For debugging purposes 582 if (mPredictionLog.size() >= MAX_LOGGED_PREDICTIONS) { 583 mPredictionLog.removeFirst(); 584 } 585 mPredictionLog.addLast(String.format("[%d,%d,%d,%f,%d]", 586 x, y, app, mMLResults, withinRange ? 1 : 0)); 587 588 // Always allow if the user is in a transient sticky immersive state 589 if (mIsNavBarShownTransiently) { 590 mLogGesture = true; 591 return withinRange; 592 } 593 594 if (mExcludeRegion.contains(x, y)) { 595 if (withinRange) { 596 // Log as exclusion only if it is in acceptable range in the first place. 597 mOverviewProxyService.notifyBackAction( 598 false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge); 599 // We don't have the end point for logging purposes. 600 mEndPoint.x = -1; 601 mEndPoint.y = -1; 602 mLogGesture = true; 603 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED); 604 } 605 return false; 606 } 607 608 mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y); 609 mLogGesture = true; 610 return withinRange; 611 } 612 cancelGesture(MotionEvent ev)613 private void cancelGesture(MotionEvent ev) { 614 // Send action cancel to reset all the touch events 615 mAllowGesture = false; 616 mLogGesture = false; 617 mInRejectedExclusion = false; 618 MotionEvent cancelEv = MotionEvent.obtain(ev); 619 cancelEv.setAction(MotionEvent.ACTION_CANCEL); 620 mEdgeBackPlugin.onMotionEvent(cancelEv); 621 cancelEv.recycle(); 622 } 623 logGesture(int backType)624 private void logGesture(int backType) { 625 if (!mLogGesture) { 626 return; 627 } 628 mLogGesture = false; 629 String logPackageName = ""; 630 // Due to privacy, only top 100 most used apps by all users can be logged. 631 if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) { 632 logPackageName = mPackageName; 633 } 634 SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType, 635 (int) mDownPoint.y, mIsOnLeftEdge 636 ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT 637 : SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT, 638 (int) mDownPoint.x, (int) mDownPoint.y, 639 (int) mEndPoint.x, (int) mEndPoint.y, 640 mEdgeWidthLeft + mLeftInset, 641 mDisplaySize.x - (mEdgeWidthRight + mRightInset), 642 mUseMLModel ? mMLResults : -2, logPackageName); 643 } 644 onMotionEvent(MotionEvent ev)645 private void onMotionEvent(MotionEvent ev) { 646 int action = ev.getActionMasked(); 647 if (action == MotionEvent.ACTION_DOWN) { 648 // Verify if this is in within the touch region and we aren't in immersive mode, and 649 // either the bouncer is showing or the notification panel is hidden 650 mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; 651 mMLResults = 0; 652 mLogGesture = false; 653 mInRejectedExclusion = false; 654 mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed 655 && !mGestureBlockingActivityRunning 656 && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) 657 && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); 658 if (mAllowGesture) { 659 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); 660 mEdgeBackPlugin.onMotionEvent(ev); 661 } 662 if (mLogGesture) { 663 mDownPoint.set(ev.getX(), ev.getY()); 664 mEndPoint.set(-1, -1); 665 mThresholdCrossed = false; 666 } 667 } else if (mAllowGesture || mLogGesture) { 668 if (!mThresholdCrossed) { 669 mEndPoint.x = (int) ev.getX(); 670 mEndPoint.y = (int) ev.getY(); 671 if (action == MotionEvent.ACTION_POINTER_DOWN) { 672 if (mAllowGesture) { 673 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH); 674 // We do not support multi touch for back gesture 675 cancelGesture(ev); 676 } 677 mLogGesture = false; 678 return; 679 } else if (action == MotionEvent.ACTION_MOVE) { 680 if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) { 681 if (mAllowGesture) { 682 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS); 683 cancelGesture(ev); 684 } 685 mLogGesture = false; 686 return; 687 } 688 float dx = Math.abs(ev.getX() - mDownPoint.x); 689 float dy = Math.abs(ev.getY() - mDownPoint.y); 690 if (dy > dx && dy > mTouchSlop) { 691 if (mAllowGesture) { 692 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE); 693 cancelGesture(ev); 694 } 695 mLogGesture = false; 696 return; 697 } else if (dx > dy && dx > mTouchSlop) { 698 if (mAllowGesture) { 699 mThresholdCrossed = true; 700 // Capture inputs 701 mInputMonitor.pilferPointers(); 702 } else { 703 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE); 704 } 705 } 706 } 707 } 708 709 if (mAllowGesture) { 710 // forward touch 711 mEdgeBackPlugin.onMotionEvent(ev); 712 } 713 } 714 715 Dependency.get(ProtoTracer.class).update(); 716 } 717 updateDisabledForQuickstep()718 private void updateDisabledForQuickstep() { 719 int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation(); 720 mDisabledForQuickstep = mStartingQuickstepRotation > -1 && 721 mStartingQuickstepRotation != rotation; 722 } 723 724 @Override onDisplayAdded(int displayId)725 public void onDisplayAdded(int displayId) { } 726 727 @Override onDisplayRemoved(int displayId)728 public void onDisplayRemoved(int displayId) { } 729 730 @Override onDisplayChanged(int displayId)731 public void onDisplayChanged(int displayId) { 732 if (mStartingQuickstepRotation > -1) { 733 updateDisabledForQuickstep(); 734 } 735 736 if (displayId == mDisplayId) { 737 updateDisplaySize(); 738 } 739 } 740 updateDisplaySize()741 private void updateDisplaySize() { 742 mContext.getDisplay().getRealSize(mDisplaySize); 743 if (mEdgeBackPlugin != null) { 744 mEdgeBackPlugin.setDisplaySize(mDisplaySize); 745 } 746 } 747 sendEvent(int action, int code)748 private void sendEvent(int action, int code) { 749 long when = SystemClock.uptimeMillis(); 750 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 751 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 752 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 753 InputDevice.SOURCE_KEYBOARD); 754 755 // Bubble controller will give us a valid display id if it should get the back event 756 BubbleController bubbleController = Dependency.get(BubbleController.class); 757 int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext); 758 if (code == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) { 759 ev.setDisplayId(bubbleDisplayId); 760 } 761 InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 762 } 763 setInsets(int leftInset, int rightInset)764 public void setInsets(int leftInset, int rightInset) { 765 mLeftInset = leftInset; 766 mRightInset = rightInset; 767 if (mEdgeBackPlugin != null) { 768 mEdgeBackPlugin.setInsets(leftInset, rightInset); 769 } 770 } 771 dump(PrintWriter pw)772 public void dump(PrintWriter pw) { 773 pw.println("EdgeBackGestureHandler:"); 774 pw.println(" mIsEnabled=" + mIsEnabled); 775 pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); 776 pw.println(" mAllowGesture=" + mAllowGesture); 777 pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); 778 pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); 779 pw.println(" mInRejectedExclusion" + mInRejectedExclusion); 780 pw.println(" mExcludeRegion=" + mExcludeRegion); 781 pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); 782 pw.println(" mIsAttached=" + mIsAttached); 783 pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); 784 pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); 785 pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); 786 pw.println(" mPredictionLog=" + String.join(";", mPredictionLog)); 787 } 788 isGestureBlockingActivityRunning()789 private boolean isGestureBlockingActivityRunning() { 790 ActivityManager.RunningTaskInfo runningTask = 791 ActivityManagerWrapper.getInstance().getRunningTask(); 792 ComponentName topActivity = runningTask == null ? null : runningTask.topActivity; 793 if (topActivity != null) { 794 mPackageName = topActivity.getPackageName(); 795 } else { 796 mPackageName = "_UNKNOWN"; 797 } 798 return topActivity != null && mGestureBlockingActivities.contains(topActivity); 799 } 800 801 @Override writeToProto(SystemUiTraceProto proto)802 public void writeToProto(SystemUiTraceProto proto) { 803 if (proto.edgeBackGestureHandler == null) { 804 proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto(); 805 } 806 proto.edgeBackGestureHandler.allowGesture = mAllowGesture; 807 } 808 809 class SysUiInputEventReceiver extends InputEventReceiver { SysUiInputEventReceiver(InputChannel channel, Looper looper)810 SysUiInputEventReceiver(InputChannel channel, Looper looper) { 811 super(channel, looper); 812 } 813 onInputEvent(InputEvent event)814 public void onInputEvent(InputEvent event) { 815 EdgeBackGestureHandler.this.onInputEvent(event); 816 finishInputEvent(event, true); 817 } 818 } 819 } 820