1 /* 2 * Copyright (C) 2020 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.navigationbar.gestural; 17 18 import static com.android.systemui.classifier.Classifier.BACK_GESTURE; 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.Configuration; 26 import android.content.res.Resources; 27 import android.graphics.PixelFormat; 28 import android.graphics.Point; 29 import android.graphics.PointF; 30 import android.graphics.Rect; 31 import android.graphics.Region; 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.Choreographer; 42 import android.view.Display; 43 import android.view.ISystemGestureExclusionListener; 44 import android.view.IWindowManager; 45 import android.view.InputDevice; 46 import android.view.InputEvent; 47 import android.view.InputMonitor; 48 import android.view.KeyCharacterMap; 49 import android.view.KeyEvent; 50 import android.view.MotionEvent; 51 import android.view.Surface; 52 import android.view.ViewConfiguration; 53 import android.view.WindowManager; 54 import android.view.WindowMetrics; 55 56 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 57 import com.android.internal.policy.GestureNavigationSettingsObserver; 58 import com.android.systemui.R; 59 import com.android.systemui.SystemUIFactory; 60 import com.android.systemui.broadcast.BroadcastDispatcher; 61 import com.android.systemui.dagger.qualifiers.Main; 62 import com.android.systemui.model.SysUiState; 63 import com.android.systemui.navigationbar.NavigationBarView; 64 import com.android.systemui.navigationbar.NavigationModeController; 65 import com.android.systemui.plugins.FalsingManager; 66 import com.android.systemui.plugins.NavigationEdgeBackPlugin; 67 import com.android.systemui.plugins.PluginListener; 68 import com.android.systemui.recents.OverviewProxyService; 69 import com.android.systemui.settings.CurrentUserTracker; 70 import com.android.systemui.shared.plugins.PluginManager; 71 import com.android.systemui.shared.system.ActivityManagerWrapper; 72 import com.android.systemui.shared.system.InputChannelCompat; 73 import com.android.systemui.shared.system.QuickStepContract; 74 import com.android.systemui.shared.system.SysUiStatsLog; 75 import com.android.systemui.shared.system.TaskStackChangeListener; 76 import com.android.systemui.shared.system.TaskStackChangeListeners; 77 import com.android.systemui.shared.tracing.ProtoTraceable; 78 import com.android.systemui.tracing.ProtoTracer; 79 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; 80 import com.android.systemui.tracing.nano.SystemUiTraceProto; 81 82 import java.io.PrintWriter; 83 import java.util.ArrayDeque; 84 import java.util.ArrayList; 85 import java.util.List; 86 import java.util.Map; 87 import java.util.concurrent.Executor; 88 89 import javax.inject.Inject; 90 91 /** 92 * Utility class to handle edge swipes for back gesture 93 */ 94 public class EdgeBackGestureHandler extends CurrentUserTracker 95 implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { 96 97 private static final String TAG = "EdgeBackGestureHandler"; 98 private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( 99 "gestures.back_timeout", 250); 100 101 private static final int MAX_NUM_LOGGED_PREDICTIONS = 10; 102 private static final int MAX_NUM_LOGGED_GESTURES = 10; 103 104 // Temporary log until b/176302696 is resolved 105 static final boolean DEBUG_MISSING_GESTURE = false; 106 static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; 107 108 private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION = 109 SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false); 110 111 private ISystemGestureExclusionListener mGestureExclusionListener = 112 new ISystemGestureExclusionListener.Stub() { 113 @Override 114 public void onSystemGestureExclusionChanged(int displayId, 115 Region systemGestureExclusion, Region unrestrictedOrNull) { 116 if (displayId == mDisplayId) { 117 mMainExecutor.execute(() -> { 118 mExcludeRegion.set(systemGestureExclusion); 119 mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null 120 ? unrestrictedOrNull : systemGestureExclusion); 121 }); 122 } 123 } 124 }; 125 126 private OverviewProxyService.OverviewProxyListener mQuickSwitchListener = 127 new OverviewProxyService.OverviewProxyListener() { 128 @Override 129 public void onPrioritizedRotation(@Surface.Rotation int rotation) { 130 mStartingQuickstepRotation = rotation; 131 updateDisabledForQuickstep(mContext.getResources().getConfiguration()); 132 } 133 }; 134 135 private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 136 @Override 137 public void onTaskStackChanged() { 138 mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); 139 } 140 @Override 141 public void onTaskCreated(int taskId, ComponentName componentName) { 142 if (componentName != null) { 143 mPackageName = componentName.getPackageName(); 144 } else { 145 mPackageName = "_UNKNOWN"; 146 } 147 } 148 149 @Override 150 public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { 151 mIsInPipMode = true; 152 } 153 154 @Override 155 public void onActivityUnpinned() { 156 mIsInPipMode = false; 157 } 158 }; 159 160 private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = 161 new DeviceConfig.OnPropertiesChangedListener() { 162 @Override 163 public void onPropertiesChanged(DeviceConfig.Properties properties) { 164 if (DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace()) 165 && (properties.getKeyset().contains( 166 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD) 167 || properties.getKeyset().contains( 168 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL) 169 || properties.getKeyset().contains( 170 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME))) { 171 updateMLModelState(); 172 } 173 } 174 }; 175 176 177 private final Context mContext; 178 private final OverviewProxyService mOverviewProxyService; 179 private final SysUiState mSysUiState; 180 private Runnable mStateChangeCallback; 181 182 private final PluginManager mPluginManager; 183 private final ProtoTracer mProtoTracer; 184 private final NavigationModeController mNavigationModeController; 185 private final ViewConfiguration mViewConfiguration; 186 private final WindowManager mWindowManager; 187 private final IWindowManager mWindowManagerService; 188 private final FalsingManager mFalsingManager; 189 // Activities which should not trigger Back gesture. 190 private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>(); 191 192 private final Point mDisplaySize = new Point(); 193 private final int mDisplayId; 194 195 private final Executor mMainExecutor; 196 197 private final Rect mPipExcludedBounds = new Rect(); 198 private final Rect mNavBarOverlayExcludedBounds = new Rect(); 199 private final Region mExcludeRegion = new Region(); 200 private final Region mUnrestrictedExcludeRegion = new Region(); 201 202 // The left side edge width where touch down is allowed 203 private int mEdgeWidthLeft; 204 // The right side edge width where touch down is allowed 205 private int mEdgeWidthRight; 206 // The bottom gesture area height 207 private float mBottomGestureHeight; 208 // The slop to distinguish between horizontal and vertical motion 209 private float mTouchSlop; 210 // Duration after which we consider the event as longpress. 211 private final int mLongPressTimeout; 212 private int mStartingQuickstepRotation = -1; 213 // We temporarily disable back gesture when user is quickswitching 214 // between apps of different orientations 215 private boolean mDisabledForQuickstep; 216 217 private final PointF mDownPoint = new PointF(); 218 private final PointF mEndPoint = new PointF(); 219 private boolean mThresholdCrossed = false; 220 private boolean mAllowGesture = false; 221 private boolean mLogGesture = false; 222 private boolean mInRejectedExclusion = false; 223 private boolean mIsOnLeftEdge; 224 225 private boolean mIsAttached; 226 private boolean mIsGesturalModeEnabled; 227 private boolean mIsEnabled; 228 private boolean mIsNavBarShownTransiently; 229 private boolean mIsBackGestureAllowed; 230 private boolean mGestureBlockingActivityRunning; 231 private boolean mIsInPipMode; 232 233 private InputMonitor mInputMonitor; 234 private InputChannelCompat.InputEventReceiver mInputEventReceiver; 235 236 private NavigationEdgeBackPlugin mEdgeBackPlugin; 237 private int mLeftInset; 238 private int mRightInset; 239 private int mSysUiFlags; 240 241 // For Tf-Lite model. 242 private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider; 243 private Map<String, Integer> mVocab; 244 private boolean mUseMLModel; 245 // minimum width below which we do not run the model 246 private int mMLEnableWidth; 247 private float mMLModelThreshold; 248 private String mPackageName; 249 private float mMLResults; 250 251 // For debugging 252 private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS); 253 private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); 254 private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); 255 256 private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; 257 258 private final NavigationEdgeBackPlugin.BackCallback mBackCallback = 259 new NavigationEdgeBackPlugin.BackCallback() { 260 @Override 261 public void triggerBack() { 262 // Notify FalsingManager that an intentional gesture has occurred. 263 // TODO(b/186519446): use a different method than isFalseTouch 264 mFalsingManager.isFalseTouch(BACK_GESTURE); 265 boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); 266 boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); 267 if (DEBUG_MISSING_GESTURE) { 268 Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown 269 + ", up=" + sendUp); 270 } 271 272 mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, 273 (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); 274 logGesture(mInRejectedExclusion 275 ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED 276 : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED); 277 } 278 279 @Override 280 public void cancelBack() { 281 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE); 282 mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x, 283 (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); 284 } 285 }; 286 287 private final SysUiState.SysUiStateCallback mSysUiStateCallback = 288 new SysUiState.SysUiStateCallback() { 289 @Override 290 public void onSystemUiStateChanged(int sysUiFlags) { 291 mSysUiFlags = sysUiFlags; 292 } 293 }; 294 295 EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, NavigationModeController navigationModeController, ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, FalsingManager falsingManager)296 EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, 297 SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, 298 BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, 299 NavigationModeController navigationModeController, ViewConfiguration viewConfiguration, 300 WindowManager windowManager, IWindowManager windowManagerService, 301 FalsingManager falsingManager) { 302 super(broadcastDispatcher); 303 mContext = context; 304 mDisplayId = context.getDisplayId(); 305 mMainExecutor = executor; 306 mOverviewProxyService = overviewProxyService; 307 mSysUiState = sysUiState; 308 mPluginManager = pluginManager; 309 mProtoTracer = protoTracer; 310 mNavigationModeController = navigationModeController; 311 mViewConfiguration = viewConfiguration; 312 mWindowManager = windowManager; 313 mWindowManagerService = windowManagerService; 314 mFalsingManager = falsingManager; 315 ComponentName recentsComponentName = ComponentName.unflattenFromString( 316 context.getString(com.android.internal.R.string.config_recentsComponentName)); 317 if (recentsComponentName != null) { 318 String recentsPackageName = recentsComponentName.getPackageName(); 319 PackageManager manager = context.getPackageManager(); 320 try { 321 Resources resources = manager.getResourcesForApplication(recentsPackageName); 322 int resId = resources.getIdentifier( 323 "gesture_blocking_activities", "array", recentsPackageName); 324 325 if (resId == 0) { 326 Log.e(TAG, "No resource found for gesture-blocking activities"); 327 } else { 328 String[] gestureBlockingActivities = resources.getStringArray(resId); 329 for (String gestureBlockingActivity : gestureBlockingActivities) { 330 mGestureBlockingActivities.add( 331 ComponentName.unflattenFromString(gestureBlockingActivity)); 332 } 333 } 334 } catch (NameNotFoundException e) { 335 Log.e(TAG, "Failed to add gesture blocking activities", e); 336 } 337 } 338 mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, 339 ViewConfiguration.getLongPressTimeout()); 340 341 mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( 342 mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged); 343 344 updateCurrentUserResources(); 345 } 346 setStateChangeCallback(Runnable callback)347 public void setStateChangeCallback(Runnable callback) { 348 mStateChangeCallback = callback; 349 } 350 updateCurrentUserResources()351 public void updateCurrentUserResources() { 352 Resources res = mNavigationModeController.getCurrentUserContext().getResources(); 353 mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); 354 mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); 355 mIsBackGestureAllowed = 356 !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); 357 358 final DisplayMetrics dm = res.getDisplayMetrics(); 359 final float defaultGestureHeight = res.getDimension( 360 com.android.internal.R.dimen.navigation_bar_gesture_height) / dm.density; 361 final float gestureHeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 362 SystemUiDeviceConfigFlags.BACK_GESTURE_BOTTOM_HEIGHT, 363 defaultGestureHeight); 364 mBottomGestureHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, gestureHeight, 365 dm); 366 367 // Set the minimum bounds to activate ML to 12dp or the minimum of configured values 368 mMLEnableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12.0f, dm); 369 if (mMLEnableWidth > mEdgeWidthRight) mMLEnableWidth = mEdgeWidthRight; 370 if (mMLEnableWidth > mEdgeWidthLeft) mMLEnableWidth = mEdgeWidthLeft; 371 372 // Reduce the default touch slop to ensure that we can intercept the gesture 373 // before the app starts to react to it. 374 // TODO(b/130352502) Tune this value and extract into a constant 375 final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 376 SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f); 377 mTouchSlop = mViewConfiguration.getScaledTouchSlop() * backGestureSlop; 378 } 379 updateNavigationBarOverlayExcludeRegion(Rect exclude)380 public void updateNavigationBarOverlayExcludeRegion(Rect exclude) { 381 mNavBarOverlayExcludedBounds.set(exclude); 382 } 383 onNavigationSettingsChanged()384 private void onNavigationSettingsChanged() { 385 boolean wasBackAllowed = isHandlingGestures(); 386 updateCurrentUserResources(); 387 if (wasBackAllowed != isHandlingGestures()) { 388 mStateChangeCallback.run(); 389 } 390 } 391 392 @Override onUserSwitched(int newUserId)393 public void onUserSwitched(int newUserId) { 394 updateIsEnabled(); 395 updateCurrentUserResources(); 396 } 397 398 /** 399 * @see NavigationBarView#onAttachedToWindow() 400 */ onNavBarAttached()401 public void onNavBarAttached() { 402 mIsAttached = true; 403 mProtoTracer.add(this); 404 mOverviewProxyService.addCallback(mQuickSwitchListener); 405 mSysUiState.addCallback(mSysUiStateCallback); 406 updateIsEnabled(); 407 startTracking(); 408 } 409 410 /** 411 * @see NavigationBarView#onDetachedFromWindow() 412 */ onNavBarDetached()413 public void onNavBarDetached() { 414 mIsAttached = false; 415 mProtoTracer.remove(this); 416 mOverviewProxyService.removeCallback(mQuickSwitchListener); 417 mSysUiState.removeCallback(mSysUiStateCallback); 418 updateIsEnabled(); 419 stopTracking(); 420 } 421 422 /** 423 * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged 424 */ onNavigationModeChanged(int mode)425 public void onNavigationModeChanged(int mode) { 426 mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode); 427 updateIsEnabled(); 428 updateCurrentUserResources(); 429 } 430 onNavBarTransientStateChanged(boolean isTransient)431 public void onNavBarTransientStateChanged(boolean isTransient) { 432 mIsNavBarShownTransiently = isTransient; 433 } 434 disposeInputChannel()435 private void disposeInputChannel() { 436 if (mInputEventReceiver != null) { 437 mInputEventReceiver.dispose(); 438 mInputEventReceiver = null; 439 } 440 if (mInputMonitor != null) { 441 mInputMonitor.dispose(); 442 mInputMonitor = null; 443 } 444 } 445 updateIsEnabled()446 private void updateIsEnabled() { 447 boolean isEnabled = mIsAttached && mIsGesturalModeEnabled; 448 if (isEnabled == mIsEnabled) { 449 return; 450 } 451 mIsEnabled = isEnabled; 452 disposeInputChannel(); 453 454 if (mEdgeBackPlugin != null) { 455 mEdgeBackPlugin.onDestroy(); 456 mEdgeBackPlugin = null; 457 } 458 459 if (!mIsEnabled) { 460 mGestureNavigationSettingsObserver.unregister(); 461 if (DEBUG_MISSING_GESTURE) { 462 Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener"); 463 } 464 mPluginManager.removePluginListener(this); 465 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 466 DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); 467 468 try { 469 mWindowManagerService.unregisterSystemGestureExclusionListener( 470 mGestureExclusionListener, mDisplayId); 471 } catch (RemoteException | IllegalArgumentException e) { 472 Log.e(TAG, "Failed to unregister window manager callbacks", e); 473 } 474 475 } else { 476 mGestureNavigationSettingsObserver.register(); 477 updateDisplaySize(); 478 if (DEBUG_MISSING_GESTURE) { 479 Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener"); 480 } 481 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 482 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, 483 mMainExecutor::execute, mOnPropertiesChangedListener); 484 485 try { 486 mWindowManagerService.registerSystemGestureExclusionListener( 487 mGestureExclusionListener, mDisplayId); 488 } catch (RemoteException | IllegalArgumentException e) { 489 Log.e(TAG, "Failed to register window manager callbacks", e); 490 } 491 492 // Register input event receiver 493 mInputMonitor = InputManager.getInstance().monitorGestureInput( 494 "edge-swipe", mDisplayId); 495 mInputEventReceiver = new InputChannelCompat.InputEventReceiver( 496 mInputMonitor.getInputChannel(), Looper.getMainLooper(), 497 Choreographer.getInstance(), this::onInputEvent); 498 499 // Add a nav bar panel window 500 setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); 501 mPluginManager.addPluginListener( 502 this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); 503 } 504 // Update the ML model resources. 505 updateMLModelState(); 506 } 507 508 @Override onPluginConnected(NavigationEdgeBackPlugin plugin, Context context)509 public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) { 510 setEdgeBackPlugin(plugin); 511 } 512 513 @Override onPluginDisconnected(NavigationEdgeBackPlugin plugin)514 public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { 515 setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); 516 } 517 setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin)518 private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { 519 if (mEdgeBackPlugin != null) { 520 mEdgeBackPlugin.onDestroy(); 521 } 522 mEdgeBackPlugin = edgeBackPlugin; 523 mEdgeBackPlugin.setBackCallback(mBackCallback); 524 mEdgeBackPlugin.setLayoutParams(createLayoutParams()); 525 updateDisplaySize(); 526 } 527 isHandlingGestures()528 public boolean isHandlingGestures() { 529 return mIsEnabled && mIsBackGestureAllowed; 530 } 531 532 /** 533 * Update the PiP bounds, used for exclusion calculation. 534 */ setPipStashExclusionBounds(Rect bounds)535 public void setPipStashExclusionBounds(Rect bounds) { 536 mPipExcludedBounds.set(bounds); 537 } 538 createLayoutParams()539 private WindowManager.LayoutParams createLayoutParams() { 540 Resources resources = mContext.getResources(); 541 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( 542 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width), 543 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height), 544 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 545 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 546 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 547 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, 548 PixelFormat.TRANSLUCENT); 549 layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel); 550 layoutParams.windowAnimations = 0; 551 layoutParams.privateFlags |= 552 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; 553 layoutParams.setTitle(TAG + mContext.getDisplayId()); 554 layoutParams.setFitInsetsTypes(0 /* types */); 555 layoutParams.setTrustedOverlay(); 556 return layoutParams; 557 } 558 onInputEvent(InputEvent ev)559 private void onInputEvent(InputEvent ev) { 560 if (!(ev instanceof MotionEvent)) return; 561 MotionEvent event = (MotionEvent) ev; 562 if (ENABLE_PER_WINDOW_INPUT_ROTATION) { 563 final Display display = mContext.getDisplay(); 564 int rotation = display.getRotation(); 565 if (rotation != Surface.ROTATION_0) { 566 Point sz = new Point(); 567 display.getRealSize(sz); 568 event = MotionEvent.obtain(event); 569 event.transform(MotionEvent.createRotateMatrix(rotation, sz.x, sz.y)); 570 } 571 } 572 onMotionEvent(event); 573 } 574 updateMLModelState()575 private void updateMLModelState() { 576 boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, 577 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false); 578 579 if (newState == mUseMLModel) { 580 return; 581 } 582 583 if (newState) { 584 String mlModelName = DeviceConfig.getString(DeviceConfig.NAMESPACE_SYSTEMUI, 585 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME, "backgesture"); 586 mBackGestureTfClassifierProvider = SystemUIFactory.getInstance() 587 .createBackGestureTfClassifierProvider(mContext.getAssets(), mlModelName); 588 mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 589 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f); 590 if (mBackGestureTfClassifierProvider.isActive()) { 591 mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets()); 592 mUseMLModel = true; 593 return; 594 } 595 } 596 597 mUseMLModel = false; 598 if (mBackGestureTfClassifierProvider != null) { 599 mBackGestureTfClassifierProvider.release(); 600 mBackGestureTfClassifierProvider = null; 601 } 602 } 603 getBackGesturePredictionsCategory(int x, int y, int app)604 private int getBackGesturePredictionsCategory(int x, int y, int app) { 605 if (app == -1) { 606 return -1; 607 } 608 int distanceFromEdge; 609 int location; 610 if (x <= mDisplaySize.x / 2.0) { 611 location = 1; // left 612 distanceFromEdge = x; 613 } else { 614 location = 2; // right 615 distanceFromEdge = mDisplaySize.x - x; 616 } 617 618 Object[] featuresVector = { 619 new long[]{(long) mDisplaySize.x}, 620 new long[]{(long) distanceFromEdge}, 621 new long[]{(long) location}, 622 new long[]{(long) app}, 623 new long[]{(long) y}, 624 }; 625 626 mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector); 627 if (mMLResults == -1) { 628 return -1; 629 } 630 return mMLResults >= mMLModelThreshold ? 1 : 0; 631 } 632 isWithinInsets(int x, int y)633 private boolean isWithinInsets(int x, int y) { 634 // Disallow if we are in the bottom gesture area 635 if (y >= (mDisplaySize.y - mBottomGestureHeight)) { 636 return false; 637 } 638 // If the point is way too far (twice the margin), it is 639 // not interesting to us for logging purposes, nor we 640 // should process it. Simply return false and keep 641 // mLogGesture = false. 642 if (x > 2 * (mEdgeWidthLeft + mLeftInset) 643 && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) { 644 return false; 645 } 646 return true; 647 } 648 isWithinTouchRegion(int x, int y)649 private boolean isWithinTouchRegion(int x, int y) { 650 // If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back 651 // gesture 652 final boolean isInsidePip = mIsInPipMode && mPipExcludedBounds.contains(x, y); 653 if (isInsidePip || mNavBarOverlayExcludedBounds.contains(x, y)) { 654 return false; 655 } 656 657 int app = -1; 658 if (mVocab != null) { 659 app = mVocab.getOrDefault(mPackageName, -1); 660 } 661 662 // Denotes whether we should proceed with the gesture. Even if it is false, we may want to 663 // log it assuming it is not invalid due to exclusion. 664 boolean withinRange = x < mEdgeWidthLeft + mLeftInset 665 || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset); 666 if (withinRange) { 667 int results = -1; 668 669 // Check if we are within the tightest bounds beyond which we would not need to run the 670 // ML model 671 boolean withinMinRange = x < mMLEnableWidth + mLeftInset 672 || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset); 673 if (!withinMinRange && mUseMLModel 674 && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) { 675 withinRange = (results == 1); 676 } 677 } 678 679 // For debugging purposes 680 mPredictionLog.log(String.format("Prediction [%d,%d,%d,%d,%f,%d]", 681 System.currentTimeMillis(), x, y, app, mMLResults, withinRange ? 1 : 0)); 682 683 // Always allow if the user is in a transient sticky immersive state 684 if (mIsNavBarShownTransiently) { 685 mLogGesture = true; 686 return withinRange; 687 } 688 689 if (mExcludeRegion.contains(x, y)) { 690 if (withinRange) { 691 // Log as exclusion only if it is in acceptable range in the first place. 692 mOverviewProxyService.notifyBackAction( 693 false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge); 694 // We don't have the end point for logging purposes. 695 mEndPoint.x = -1; 696 mEndPoint.y = -1; 697 mLogGesture = true; 698 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED); 699 } 700 return false; 701 } 702 703 mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y); 704 mLogGesture = true; 705 return withinRange; 706 } 707 cancelGesture(MotionEvent ev)708 private void cancelGesture(MotionEvent ev) { 709 // Send action cancel to reset all the touch events 710 mAllowGesture = false; 711 mLogGesture = false; 712 mInRejectedExclusion = false; 713 MotionEvent cancelEv = MotionEvent.obtain(ev); 714 cancelEv.setAction(MotionEvent.ACTION_CANCEL); 715 mEdgeBackPlugin.onMotionEvent(cancelEv); 716 cancelEv.recycle(); 717 } 718 logGesture(int backType)719 private void logGesture(int backType) { 720 if (!mLogGesture) { 721 return; 722 } 723 mLogGesture = false; 724 String logPackageName = ""; 725 // Due to privacy, only top 100 most used apps by all users can be logged. 726 if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) { 727 logPackageName = mPackageName; 728 } 729 SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType, 730 (int) mDownPoint.y, mIsOnLeftEdge 731 ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT 732 : SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT, 733 (int) mDownPoint.x, (int) mDownPoint.y, 734 (int) mEndPoint.x, (int) mEndPoint.y, 735 mEdgeWidthLeft + mLeftInset, 736 mDisplaySize.x - (mEdgeWidthRight + mRightInset), 737 mUseMLModel ? mMLResults : -2, logPackageName); 738 } 739 onMotionEvent(MotionEvent ev)740 private void onMotionEvent(MotionEvent ev) { 741 int action = ev.getActionMasked(); 742 if (action == MotionEvent.ACTION_DOWN) { 743 if (DEBUG_MISSING_GESTURE) { 744 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev); 745 } 746 747 // Verify if this is in within the touch region and we aren't in immersive mode, and 748 // either the bouncer is showing or the notification panel is hidden 749 mInputEventReceiver.setBatchingEnabled(false); 750 mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; 751 mMLResults = 0; 752 mLogGesture = false; 753 mInRejectedExclusion = false; 754 boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); 755 mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets 756 && !mGestureBlockingActivityRunning 757 && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) 758 && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); 759 if (mAllowGesture) { 760 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); 761 mEdgeBackPlugin.onMotionEvent(ev); 762 } 763 if (mLogGesture) { 764 mDownPoint.set(ev.getX(), ev.getY()); 765 mEndPoint.set(-1, -1); 766 mThresholdCrossed = false; 767 } 768 769 // For debugging purposes, only log edge points 770 (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format( 771 "Gesture [%d,alw=%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]", 772 System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge, 773 mIsBackGestureAllowed, 774 QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize, 775 mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); 776 } else if (mAllowGesture || mLogGesture) { 777 if (!mThresholdCrossed) { 778 mEndPoint.x = (int) ev.getX(); 779 mEndPoint.y = (int) ev.getY(); 780 if (action == MotionEvent.ACTION_POINTER_DOWN) { 781 if (mAllowGesture) { 782 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH); 783 if (DEBUG_MISSING_GESTURE) { 784 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back: multitouch"); 785 } 786 // We do not support multi touch for back gesture 787 cancelGesture(ev); 788 } 789 mLogGesture = false; 790 return; 791 } else if (action == MotionEvent.ACTION_MOVE) { 792 if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) { 793 if (mAllowGesture) { 794 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS); 795 cancelGesture(ev); 796 if (DEBUG_MISSING_GESTURE) { 797 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back [longpress]: " 798 + ev.getEventTime() 799 + " " + ev.getDownTime() 800 + " " + mLongPressTimeout); 801 } 802 } 803 mLogGesture = false; 804 return; 805 } 806 float dx = Math.abs(ev.getX() - mDownPoint.x); 807 float dy = Math.abs(ev.getY() - mDownPoint.y); 808 if (dy > dx && dy > mTouchSlop) { 809 if (mAllowGesture) { 810 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE); 811 cancelGesture(ev); 812 if (DEBUG_MISSING_GESTURE) { 813 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back [vertical move]: " 814 + dy + " " + dx + " " + mTouchSlop); 815 } 816 } 817 mLogGesture = false; 818 return; 819 } else if (dx > dy && dx > mTouchSlop) { 820 if (mAllowGesture) { 821 mThresholdCrossed = true; 822 // Capture inputs 823 mInputMonitor.pilferPointers(); 824 mInputEventReceiver.setBatchingEnabled(true); 825 } else { 826 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE); 827 } 828 } 829 } 830 } 831 832 if (mAllowGesture) { 833 // forward touch 834 mEdgeBackPlugin.onMotionEvent(ev); 835 } 836 } 837 838 mProtoTracer.scheduleFrameUpdate(); 839 } 840 updateDisabledForQuickstep(Configuration newConfig)841 private void updateDisabledForQuickstep(Configuration newConfig) { 842 int rotation = newConfig.windowConfiguration.getRotation(); 843 mDisabledForQuickstep = mStartingQuickstepRotation > -1 && 844 mStartingQuickstepRotation != rotation; 845 } 846 onConfigurationChanged(Configuration newConfig)847 public void onConfigurationChanged(Configuration newConfig) { 848 if (mStartingQuickstepRotation > -1) { 849 updateDisabledForQuickstep(newConfig); 850 } 851 852 if (DEBUG_MISSING_GESTURE) { 853 Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: config=" + newConfig); 854 } 855 updateDisplaySize(); 856 } 857 updateDisplaySize()858 private void updateDisplaySize() { 859 WindowMetrics metrics = mWindowManager.getMaximumWindowMetrics(); 860 Rect bounds = metrics.getBounds(); 861 mDisplaySize.set(bounds.width(), bounds.height()); 862 if (DEBUG_MISSING_GESTURE) { 863 Log.d(DEBUG_MISSING_GESTURE_TAG, "Update display size: mDisplaySize=" + mDisplaySize); 864 } 865 if (mEdgeBackPlugin != null) { 866 mEdgeBackPlugin.setDisplaySize(mDisplaySize); 867 } 868 } 869 sendEvent(int action, int code)870 private boolean sendEvent(int action, int code) { 871 long when = SystemClock.uptimeMillis(); 872 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 873 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 874 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 875 InputDevice.SOURCE_KEYBOARD); 876 877 ev.setDisplayId(mContext.getDisplay().getDisplayId()); 878 return InputManager.getInstance() 879 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 880 } 881 setInsets(int leftInset, int rightInset)882 public void setInsets(int leftInset, int rightInset) { 883 mLeftInset = leftInset; 884 mRightInset = rightInset; 885 if (mEdgeBackPlugin != null) { 886 mEdgeBackPlugin.setInsets(leftInset, rightInset); 887 } 888 } 889 dump(PrintWriter pw)890 public void dump(PrintWriter pw) { 891 pw.println("EdgeBackGestureHandler:"); 892 pw.println(" mIsEnabled=" + mIsEnabled); 893 pw.println(" mIsAttached=" + mIsAttached); 894 pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); 895 pw.println(" mIsGesturalModeEnabled=" + mIsGesturalModeEnabled); 896 pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); 897 pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning); 898 pw.println(" mAllowGesture=" + mAllowGesture); 899 pw.println(" mUseMLModel=" + mUseMLModel); 900 pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); 901 pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); 902 pw.println(" mInRejectedExclusion=" + mInRejectedExclusion); 903 pw.println(" mExcludeRegion=" + mExcludeRegion); 904 pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); 905 pw.println(" mIsInPipMode=" + mIsInPipMode); 906 pw.println(" mPipExcludedBounds=" + mPipExcludedBounds); 907 pw.println(" mNavBarOverlayExcludedBounds=" + mNavBarOverlayExcludedBounds); 908 pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); 909 pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); 910 pw.println(" mLeftInset=" + mLeftInset); 911 pw.println(" mRightInset=" + mRightInset); 912 pw.println(" mMLEnableWidth=" + mMLEnableWidth); 913 pw.println(" mMLModelThreshold=" + mMLModelThreshold); 914 pw.println(" mTouchSlop=" + mTouchSlop); 915 pw.println(" mBottomGestureHeight=" + mBottomGestureHeight); 916 pw.println(" mPredictionLog=" + String.join("\n", mPredictionLog)); 917 pw.println(" mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets)); 918 pw.println(" mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets)); 919 pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin); 920 if (mEdgeBackPlugin != null) { 921 mEdgeBackPlugin.dump(pw); 922 } 923 } 924 isGestureBlockingActivityRunning()925 private boolean isGestureBlockingActivityRunning() { 926 ActivityManager.RunningTaskInfo runningTask = 927 ActivityManagerWrapper.getInstance().getRunningTask(); 928 ComponentName topActivity = runningTask == null ? null : runningTask.topActivity; 929 if (topActivity != null) { 930 mPackageName = topActivity.getPackageName(); 931 } else { 932 mPackageName = "_UNKNOWN"; 933 } 934 return topActivity != null && mGestureBlockingActivities.contains(topActivity); 935 } 936 937 @Override writeToProto(SystemUiTraceProto proto)938 public void writeToProto(SystemUiTraceProto proto) { 939 if (proto.edgeBackGestureHandler == null) { 940 proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto(); 941 } 942 proto.edgeBackGestureHandler.allowGesture = mAllowGesture; 943 } 944 945 /** 946 * Injectable instance to create a new EdgeBackGestureHandler. 947 * 948 * Necessary because we don't have good handling of per-display contexts at the moment. With 949 * this, you can pass in a specific context that knows what display it is in. 950 */ 951 public static class Factory { 952 private final OverviewProxyService mOverviewProxyService; 953 private final SysUiState mSysUiState; 954 private final PluginManager mPluginManager; 955 private final Executor mExecutor; 956 private final BroadcastDispatcher mBroadcastDispatcher; 957 private final ProtoTracer mProtoTracer; 958 private final NavigationModeController mNavigationModeController; 959 private final ViewConfiguration mViewConfiguration; 960 private final WindowManager mWindowManager; 961 private final IWindowManager mWindowManagerService; 962 private final FalsingManager mFalsingManager; 963 964 @Inject Factory(OverviewProxyService overviewProxyService, SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, NavigationModeController navigationModeController, ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, FalsingManager falsingManager)965 public Factory(OverviewProxyService overviewProxyService, 966 SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, 967 BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, 968 NavigationModeController navigationModeController, 969 ViewConfiguration viewConfiguration, WindowManager windowManager, 970 IWindowManager windowManagerService, FalsingManager falsingManager) { 971 mOverviewProxyService = overviewProxyService; 972 mSysUiState = sysUiState; 973 mPluginManager = pluginManager; 974 mExecutor = executor; 975 mBroadcastDispatcher = broadcastDispatcher; 976 mProtoTracer = protoTracer; 977 mNavigationModeController = navigationModeController; 978 mViewConfiguration = viewConfiguration; 979 mWindowManager = windowManager; 980 mWindowManagerService = windowManagerService; 981 mFalsingManager = falsingManager; 982 } 983 984 /** Construct a {@link EdgeBackGestureHandler}. */ create(Context context)985 public EdgeBackGestureHandler create(Context context) { 986 return new EdgeBackGestureHandler(context, mOverviewProxyService, mSysUiState, 987 mPluginManager, mExecutor, mBroadcastDispatcher, mProtoTracer, 988 mNavigationModeController, mViewConfiguration, mWindowManager, 989 mWindowManagerService, mFalsingManager); 990 } 991 } 992 993 private static class LogArray extends ArrayDeque<String> { 994 private final int mLength; 995 LogArray(int length)996 LogArray(int length) { 997 mLength = length; 998 } 999 log(String message)1000 void log(String message) { 1001 if (size() >= mLength) { 1002 removeFirst(); 1003 } 1004 addLast(message); 1005 if (DEBUG_MISSING_GESTURE) { 1006 Log.d(DEBUG_MISSING_GESTURE_TAG, message); 1007 } 1008 } 1009 } 1010 } 1011