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