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.InputDevice.SOURCE_MOUSE; 19 import static android.view.InputDevice.SOURCE_TOUCHPAD; 20 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; 21 22 import static com.android.systemui.classifier.Classifier.BACK_GESTURE; 23 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll; 24 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe; 25 26 import android.annotation.NonNull; 27 import android.app.ActivityManager; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.content.res.Configuration; 33 import android.content.res.Resources; 34 import android.graphics.Insets; 35 import android.graphics.PixelFormat; 36 import android.graphics.Point; 37 import android.graphics.PointF; 38 import android.graphics.Rect; 39 import android.graphics.Region; 40 import android.hardware.input.InputManager; 41 import android.icu.text.SimpleDateFormat; 42 import android.os.Handler; 43 import android.os.Looper; 44 import android.os.RemoteException; 45 import android.os.SystemClock; 46 import android.os.SystemProperties; 47 import android.os.Trace; 48 import android.provider.DeviceConfig; 49 import android.util.DisplayMetrics; 50 import android.util.Log; 51 import android.util.TypedValue; 52 import android.view.Choreographer; 53 import android.view.ISystemGestureExclusionListener; 54 import android.view.IWindowManager; 55 import android.view.InputDevice; 56 import android.view.InputEvent; 57 import android.view.InputMonitor; 58 import android.view.KeyCharacterMap; 59 import android.view.KeyEvent; 60 import android.view.MotionEvent; 61 import android.view.Surface; 62 import android.view.VelocityTracker; 63 import android.view.ViewConfiguration; 64 import android.view.WindowInsets; 65 import android.view.WindowManager; 66 import android.window.BackEvent; 67 68 import androidx.annotation.DimenRes; 69 70 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 71 import com.android.internal.policy.GestureNavigationSettingsObserver; 72 import com.android.systemui.R; 73 import com.android.systemui.dagger.qualifiers.Background; 74 import com.android.systemui.dagger.qualifiers.Main; 75 import com.android.systemui.flags.FeatureFlags; 76 import com.android.systemui.flags.Flags; 77 import com.android.systemui.model.SysUiState; 78 import com.android.systemui.navigationbar.NavigationModeController; 79 import com.android.systemui.plugins.FalsingManager; 80 import com.android.systemui.plugins.NavigationEdgeBackPlugin; 81 import com.android.systemui.plugins.PluginListener; 82 import com.android.systemui.plugins.PluginManager; 83 import com.android.systemui.recents.OverviewProxyService; 84 import com.android.systemui.settings.UserTracker; 85 import com.android.systemui.shared.system.ActivityManagerWrapper; 86 import com.android.systemui.shared.system.InputChannelCompat; 87 import com.android.systemui.shared.system.QuickStepContract; 88 import com.android.systemui.shared.system.SysUiStatsLog; 89 import com.android.systemui.shared.system.TaskStackChangeListener; 90 import com.android.systemui.shared.system.TaskStackChangeListeners; 91 import com.android.systemui.statusbar.phone.LightBarController; 92 import com.android.systemui.util.Assert; 93 import com.android.wm.shell.back.BackAnimation; 94 import com.android.wm.shell.desktopmode.DesktopMode; 95 import com.android.wm.shell.pip.Pip; 96 97 import java.io.PrintWriter; 98 import java.util.ArrayDeque; 99 import java.util.ArrayList; 100 import java.util.Date; 101 import java.util.List; 102 import java.util.Locale; 103 import java.util.Map; 104 import java.util.Optional; 105 import java.util.concurrent.Executor; 106 import java.util.function.Consumer; 107 108 import javax.inject.Inject; 109 import javax.inject.Provider; 110 111 /** 112 * Utility class to handle edge swipes for back gesture 113 */ 114 public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin> { 115 116 private static final String TAG = "EdgeBackGestureHandler"; 117 private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( 118 "gestures.back_timeout", 250); 119 120 private static final int MAX_NUM_LOGGED_PREDICTIONS = 10; 121 private static final int MAX_NUM_LOGGED_GESTURES = 10; 122 123 static final boolean DEBUG_MISSING_GESTURE = false; 124 public static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; 125 126 private ISystemGestureExclusionListener mGestureExclusionListener = 127 new ISystemGestureExclusionListener.Stub() { 128 @Override 129 public void onSystemGestureExclusionChanged(int displayId, 130 Region systemGestureExclusion, Region unrestrictedOrNull) { 131 if (displayId == mDisplayId) { 132 mMainExecutor.execute(() -> { 133 mExcludeRegion.set(systemGestureExclusion); 134 mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null 135 ? unrestrictedOrNull : systemGestureExclusion); 136 }); 137 } 138 } 139 }; 140 141 private OverviewProxyService.OverviewProxyListener mQuickSwitchListener = 142 new OverviewProxyService.OverviewProxyListener() { 143 @Override 144 public void onPrioritizedRotation(@Surface.Rotation int rotation) { 145 mStartingQuickstepRotation = rotation; 146 updateDisabledForQuickstep(mLastReportedConfig); 147 } 148 }; 149 150 private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 151 @Override 152 public void onTaskStackChanged() { 153 mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); 154 } 155 @Override 156 public void onTaskCreated(int taskId, ComponentName componentName) { 157 if (componentName != null) { 158 mPackageName = componentName.getPackageName(); 159 } else { 160 mPackageName = "_UNKNOWN"; 161 } 162 } 163 }; 164 165 private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = 166 new DeviceConfig.OnPropertiesChangedListener() { 167 @Override 168 public void onPropertiesChanged(DeviceConfig.Properties properties) { 169 if (DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace()) 170 && (properties.getKeyset().contains( 171 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD) 172 || properties.getKeyset().contains( 173 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL) 174 || properties.getKeyset().contains( 175 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME))) { 176 updateMLModelState(); 177 } 178 } 179 }; 180 181 private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 182 private final Context mContext; 183 private final UserTracker mUserTracker; 184 private final OverviewProxyService mOverviewProxyService; 185 private final SysUiState mSysUiState; 186 private Runnable mStateChangeCallback; 187 private Consumer<Boolean> mButtonForcedVisibleCallback; 188 189 private final PluginManager mPluginManager; 190 private final NavigationModeController mNavigationModeController; 191 private final BackPanelController.Factory mBackPanelControllerFactory; 192 private final ViewConfiguration mViewConfiguration; 193 private final WindowManager mWindowManager; 194 private final IWindowManager mWindowManagerService; 195 private final InputManager mInputManager; 196 private final Optional<Pip> mPipOptional; 197 private final Optional<DesktopMode> mDesktopModeOptional; 198 private final FalsingManager mFalsingManager; 199 private final Configuration mLastReportedConfig = new Configuration(); 200 // Activities which should not trigger Back gesture. 201 private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>(); 202 203 private final Point mDisplaySize = new Point(); 204 private final int mDisplayId; 205 206 private final Executor mMainExecutor; 207 private final Handler mMainHandler; 208 private final Executor mBackgroundExecutor; 209 210 private final Rect mPipExcludedBounds = new Rect(); 211 private final Rect mNavBarOverlayExcludedBounds = new Rect(); 212 private final Region mExcludeRegion = new Region(); 213 private final Region mDesktopModeExcludeRegion = new Region(); 214 private final Region mUnrestrictedExcludeRegion = new Region(); 215 private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider; 216 private final Provider<BackGestureTfClassifierProvider> 217 mBackGestureTfClassifierProviderProvider; 218 private final FeatureFlags mFeatureFlags; 219 private final Provider<LightBarController> mLightBarControllerProvider; 220 221 // The left side edge width where touch down is allowed 222 private int mEdgeWidthLeft; 223 // The right side edge width where touch down is allowed 224 private int mEdgeWidthRight; 225 // The bottom gesture area height 226 private float mBottomGestureHeight; 227 // The slop to distinguish between horizontal and vertical motion 228 private float mTouchSlop; 229 // The threshold for back swipe full progress. 230 private float mBackSwipeLinearThreshold; 231 private float mNonLinearFactor; 232 // Duration after which we consider the event as longpress. 233 private final int mLongPressTimeout; 234 private int mStartingQuickstepRotation = -1; 235 // We temporarily disable back gesture when user is quickswitching 236 // between apps of different orientations 237 private boolean mDisabledForQuickstep; 238 // This gets updated when the value of PipTransitionState#isInPip changes. 239 private boolean mIsInPip; 240 241 private final PointF mDownPoint = new PointF(); 242 private final PointF mEndPoint = new PointF(); 243 private boolean mThresholdCrossed = false; 244 private boolean mAllowGesture = false; 245 private boolean mLogGesture = false; 246 private boolean mInRejectedExclusion = false; 247 private boolean mIsOnLeftEdge; 248 private boolean mDeferSetIsOnLeftEdge; 249 250 private boolean mIsAttached; 251 private boolean mIsGestureHandlingEnabled; 252 private boolean mIsTrackpadConnected; 253 private boolean mInGestureNavMode; 254 private boolean mUsingThreeButtonNav; 255 private boolean mIsEnabled; 256 private boolean mIsNavBarShownTransiently; 257 private boolean mIsBackGestureAllowed; 258 private boolean mGestureBlockingActivityRunning; 259 private boolean mIsNewBackAffordanceEnabled; 260 private boolean mIsTrackpadGestureFeaturesEnabled; 261 private boolean mIsTrackpadThreeFingerSwipe; 262 private boolean mIsButtonForcedVisible; 263 264 private InputMonitor mInputMonitor; 265 private InputChannelCompat.InputEventReceiver mInputEventReceiver; 266 267 private NavigationEdgeBackPlugin mEdgeBackPlugin; 268 private BackAnimation mBackAnimation; 269 private int mLeftInset; 270 private int mRightInset; 271 private int mSysUiFlags; 272 273 // For Tf-Lite model. 274 private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider; 275 private Map<String, Integer> mVocab; 276 private boolean mUseMLModel; 277 private boolean mMLModelIsLoading; 278 // minimum width below which we do not run the model 279 private int mMLEnableWidth; 280 private float mMLModelThreshold; 281 private String mPackageName; 282 private float mMLResults; 283 284 // For debugging 285 private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS); 286 private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); 287 private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES); 288 private SimpleDateFormat mLogDateFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US); 289 private Date mTmpLogDate = new Date(); 290 291 private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; 292 293 private final NavigationEdgeBackPlugin.BackCallback mBackCallback = 294 new NavigationEdgeBackPlugin.BackCallback() { 295 @Override 296 public void triggerBack() { 297 // Notify FalsingManager that an intentional gesture has occurred. 298 mFalsingManager.isFalseTouch(BACK_GESTURE); 299 // Only inject back keycodes when ahead-of-time back dispatching is disabled. 300 if (mBackAnimation == null) { 301 boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); 302 boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); 303 if (DEBUG_MISSING_GESTURE) { 304 Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" 305 + sendDown + ", up=" + sendUp); 306 } 307 } else { 308 mBackAnimation.setTriggerBack(true); 309 } 310 311 logGesture(mInRejectedExclusion 312 ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED 313 : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED); 314 } 315 316 @Override 317 public void cancelBack() { 318 if (mBackAnimation != null) { 319 mBackAnimation.setTriggerBack(false); 320 } 321 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE); 322 } 323 324 @Override 325 public void setTriggerBack(boolean triggerBack) { 326 if (mBackAnimation != null) { 327 mBackAnimation.setTriggerBack(triggerBack); 328 } 329 } 330 }; 331 332 private final SysUiState.SysUiStateCallback mSysUiStateCallback = 333 new SysUiState.SysUiStateCallback() { 334 @Override 335 public void onSystemUiStateChanged(int sysUiFlags) { 336 mSysUiFlags = sysUiFlags; 337 } 338 }; 339 340 private final Consumer<Boolean> mOnIsInPipStateChangedListener = 341 (isInPip) -> mIsInPip = isInPip; 342 343 private final Consumer<Region> mDesktopCornersChangedListener = 344 (desktopExcludeRegion) -> mDesktopModeExcludeRegion.set(desktopExcludeRegion); 345 346 private final UserTracker.Callback mUserChangedCallback = 347 new UserTracker.Callback() { 348 @Override 349 public void onUserChanged(int newUser, @NonNull Context userContext) { 350 updateIsEnabled(); 351 updateCurrentUserResources(); 352 } 353 }; 354 355 private final InputManager.InputDeviceListener mInputDeviceListener = 356 new InputManager.InputDeviceListener() { 357 358 // Only one trackpad can be connected to a device at a time, since it takes over the 359 // only USB port. 360 private int mTrackpadDeviceId; 361 362 @Override 363 public void onInputDeviceAdded(int deviceId) { 364 if (isTrackpadDevice(deviceId)) { 365 mTrackpadDeviceId = deviceId; 366 update(true /* isTrackpadConnected */); 367 } 368 } 369 370 @Override 371 public void onInputDeviceChanged(int deviceId) { } 372 373 @Override 374 public void onInputDeviceRemoved(int deviceId) { 375 if (mTrackpadDeviceId == deviceId) { 376 update(false /* isTrackpadConnected */); 377 } 378 } 379 380 private void update(boolean isTrackpadConnected) { 381 boolean isPreviouslyTrackpadConnected = mIsTrackpadConnected; 382 mIsTrackpadConnected = isTrackpadConnected; 383 if (isPreviouslyTrackpadConnected != mIsTrackpadConnected) { 384 updateIsEnabled(); 385 updateCurrentUserResources(); 386 } 387 } 388 }; 389 EdgeBackGestureHandler( Context context, OverviewProxyService overviewProxyService, SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, @Main Handler handler, @Background Executor backgroundExecutor, UserTracker userTracker, NavigationModeController navigationModeController, BackPanelController.Factory backPanelControllerFactory, ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, InputManager inputManager, Optional<Pip> pipOptional, Optional<DesktopMode> desktopModeOptional, FalsingManager falsingManager, Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider, Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider, FeatureFlags featureFlags, Provider<LightBarController> lightBarControllerProvider)390 EdgeBackGestureHandler( 391 Context context, 392 OverviewProxyService overviewProxyService, 393 SysUiState sysUiState, 394 PluginManager pluginManager, 395 @Main Executor executor, 396 @Main Handler handler, 397 @Background Executor backgroundExecutor, 398 UserTracker userTracker, 399 NavigationModeController navigationModeController, 400 BackPanelController.Factory backPanelControllerFactory, 401 ViewConfiguration viewConfiguration, 402 WindowManager windowManager, 403 IWindowManager windowManagerService, 404 InputManager inputManager, 405 Optional<Pip> pipOptional, 406 Optional<DesktopMode> desktopModeOptional, 407 FalsingManager falsingManager, 408 Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider, 409 Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider, 410 FeatureFlags featureFlags, 411 Provider<LightBarController> lightBarControllerProvider) { 412 mContext = context; 413 mDisplayId = context.getDisplayId(); 414 mMainExecutor = executor; 415 mMainHandler = handler; 416 mBackgroundExecutor = backgroundExecutor; 417 mUserTracker = userTracker; 418 mOverviewProxyService = overviewProxyService; 419 mSysUiState = sysUiState; 420 mPluginManager = pluginManager; 421 mNavigationModeController = navigationModeController; 422 mBackPanelControllerFactory = backPanelControllerFactory; 423 mViewConfiguration = viewConfiguration; 424 mWindowManager = windowManager; 425 mWindowManagerService = windowManagerService; 426 mInputManager = inputManager; 427 mPipOptional = pipOptional; 428 mDesktopModeOptional = desktopModeOptional; 429 mFalsingManager = falsingManager; 430 mNavBarEdgePanelProvider = navigationBarEdgePanelProvider; 431 mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider; 432 mFeatureFlags = featureFlags; 433 mLightBarControllerProvider = lightBarControllerProvider; 434 mLastReportedConfig.setTo(mContext.getResources().getConfiguration()); 435 mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled( 436 Flags.TRACKPAD_GESTURE_FEATURES); 437 ComponentName recentsComponentName = ComponentName.unflattenFromString( 438 context.getString(com.android.internal.R.string.config_recentsComponentName)); 439 if (recentsComponentName != null) { 440 String recentsPackageName = recentsComponentName.getPackageName(); 441 PackageManager manager = context.getPackageManager(); 442 try { 443 Resources resources = manager.getResourcesForApplication( 444 manager.getApplicationInfo(recentsPackageName, 445 PackageManager.MATCH_UNINSTALLED_PACKAGES 446 | PackageManager.MATCH_DISABLED_COMPONENTS 447 | PackageManager.GET_SHARED_LIBRARY_FILES)); 448 int resId = resources.getIdentifier( 449 "gesture_blocking_activities", "array", recentsPackageName); 450 451 if (resId == 0) { 452 Log.e(TAG, "No resource found for gesture-blocking activities"); 453 } else { 454 String[] gestureBlockingActivities = resources.getStringArray(resId); 455 for (String gestureBlockingActivity : gestureBlockingActivities) { 456 mGestureBlockingActivities.add( 457 ComponentName.unflattenFromString(gestureBlockingActivity)); 458 } 459 } 460 } catch (NameNotFoundException e) { 461 Log.e(TAG, "Failed to add gesture blocking activities", e); 462 } 463 } 464 mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, 465 ViewConfiguration.getLongPressTimeout()); 466 467 mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( 468 mMainHandler, mContext, this::onNavigationSettingsChanged); 469 470 updateCurrentUserResources(); 471 } 472 setStateChangeCallback(Runnable callback)473 public void setStateChangeCallback(Runnable callback) { 474 mStateChangeCallback = callback; 475 } 476 setButtonForcedVisibleChangeCallback(Consumer<Boolean> callback)477 public void setButtonForcedVisibleChangeCallback(Consumer<Boolean> callback) { 478 mButtonForcedVisibleCallback = callback; 479 } 480 getEdgeWidthLeft()481 public int getEdgeWidthLeft() { 482 return mEdgeWidthLeft; 483 } 484 getEdgeWidthRight()485 public int getEdgeWidthRight() { 486 return mEdgeWidthRight; 487 } 488 updateCurrentUserResources()489 public void updateCurrentUserResources() { 490 Resources res = mNavigationModeController.getCurrentUserContext().getResources(); 491 mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); 492 mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); 493 final boolean previousForcedVisible = mIsButtonForcedVisible; 494 mIsButtonForcedVisible = 495 mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); 496 if (previousForcedVisible != mIsButtonForcedVisible 497 && mButtonForcedVisibleCallback != null) { 498 mButtonForcedVisibleCallback.accept(mIsButtonForcedVisible); 499 } 500 mIsBackGestureAllowed = !mIsButtonForcedVisible; 501 502 final DisplayMetrics dm = res.getDisplayMetrics(); 503 final float defaultGestureHeight = res.getDimension( 504 com.android.internal.R.dimen.navigation_bar_gesture_height) / dm.density; 505 final float gestureHeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 506 SystemUiDeviceConfigFlags.BACK_GESTURE_BOTTOM_HEIGHT, 507 defaultGestureHeight); 508 mBottomGestureHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, gestureHeight, 509 dm); 510 511 // Set the minimum bounds to activate ML to 12dp or the minimum of configured values 512 mMLEnableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12.0f, dm); 513 if (mMLEnableWidth > mEdgeWidthRight) mMLEnableWidth = mEdgeWidthRight; 514 if (mMLEnableWidth > mEdgeWidthLeft) mMLEnableWidth = mEdgeWidthLeft; 515 516 // Reduce the default touch slop to ensure that we can intercept the gesture 517 // before the app starts to react to it. 518 // TODO(b/130352502) Tune this value and extract into a constant 519 final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 520 SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f); 521 mTouchSlop = mViewConfiguration.getScaledTouchSlop() * backGestureSlop; 522 mBackSwipeLinearThreshold = res.getDimension( 523 R.dimen.navigation_edge_action_progress_threshold); 524 mNonLinearFactor = getDimenFloat(res, 525 R.dimen.back_progress_non_linear_factor); 526 updateBackAnimationThresholds(); 527 } 528 getDimenFloat(Resources res, @DimenRes int resId)529 private float getDimenFloat(Resources res, @DimenRes int resId) { 530 TypedValue typedValue = new TypedValue(); 531 res.getValue(resId, typedValue, true); 532 return typedValue.getFloat(); 533 } 534 updateNavigationBarOverlayExcludeRegion(Rect exclude)535 public void updateNavigationBarOverlayExcludeRegion(Rect exclude) { 536 mNavBarOverlayExcludedBounds.set(exclude); 537 } 538 onNavigationSettingsChanged()539 private void onNavigationSettingsChanged() { 540 boolean wasBackAllowed = isHandlingGestures(); 541 updateCurrentUserResources(); 542 if (mStateChangeCallback != null && wasBackAllowed != isHandlingGestures()) { 543 mStateChangeCallback.run(); 544 } 545 } 546 547 /** 548 * Called when the nav/task bar is attached. 549 */ onNavBarAttached()550 public void onNavBarAttached() { 551 mIsAttached = true; 552 mOverviewProxyService.addCallback(mQuickSwitchListener); 553 mSysUiState.addCallback(mSysUiStateCallback); 554 if (mIsTrackpadGestureFeaturesEnabled) { 555 mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler); 556 int [] inputDevices = mInputManager.getInputDeviceIds(); 557 for (int inputDeviceId : inputDevices) { 558 mInputDeviceListener.onInputDeviceAdded(inputDeviceId); 559 } 560 } 561 updateIsEnabled(); 562 mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); 563 } 564 565 /** 566 * Called when the nav/task bar is detached. 567 */ onNavBarDetached()568 public void onNavBarDetached() { 569 mIsAttached = false; 570 mOverviewProxyService.removeCallback(mQuickSwitchListener); 571 mSysUiState.removeCallback(mSysUiStateCallback); 572 mInputManager.unregisterInputDeviceListener(mInputDeviceListener); 573 updateIsEnabled(); 574 mUserTracker.removeCallback(mUserChangedCallback); 575 } 576 577 /** 578 * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged 579 */ onNavigationModeChanged(int mode)580 public void onNavigationModeChanged(int mode) { 581 mUsingThreeButtonNav = QuickStepContract.isLegacyMode(mode); 582 mInGestureNavMode = QuickStepContract.isGesturalMode(mode); 583 updateIsEnabled(); 584 updateCurrentUserResources(); 585 } 586 onNavBarTransientStateChanged(boolean isTransient)587 public void onNavBarTransientStateChanged(boolean isTransient) { 588 mIsNavBarShownTransiently = isTransient; 589 } 590 disposeInputChannel()591 private void disposeInputChannel() { 592 if (mInputEventReceiver != null) { 593 mInputEventReceiver.dispose(); 594 mInputEventReceiver = null; 595 } 596 if (mInputMonitor != null) { 597 mInputMonitor.dispose(); 598 mInputMonitor = null; 599 } 600 } 601 updateIsEnabled()602 private void updateIsEnabled() { 603 try { 604 Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled"); 605 606 mIsGestureHandlingEnabled = 607 mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav 608 && mIsTrackpadConnected); 609 boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled; 610 if (isEnabled == mIsEnabled) { 611 return; 612 } 613 mIsEnabled = isEnabled; 614 disposeInputChannel(); 615 616 if (mEdgeBackPlugin != null) { 617 mEdgeBackPlugin.onDestroy(); 618 mEdgeBackPlugin = null; 619 } 620 621 if (!mIsEnabled) { 622 mGestureNavigationSettingsObserver.unregister(); 623 if (DEBUG_MISSING_GESTURE) { 624 Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener"); 625 } 626 mPluginManager.removePluginListener(this); 627 TaskStackChangeListeners.getInstance().unregisterTaskStackListener( 628 mTaskStackListener); 629 DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); 630 mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null)); 631 632 try { 633 mWindowManagerService.unregisterSystemGestureExclusionListener( 634 mGestureExclusionListener, mDisplayId); 635 } catch (RemoteException | IllegalArgumentException e) { 636 Log.e(TAG, "Failed to unregister window manager callbacks", e); 637 } 638 639 } else { 640 mGestureNavigationSettingsObserver.register(); 641 updateDisplaySize(); 642 if (DEBUG_MISSING_GESTURE) { 643 Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener"); 644 } 645 TaskStackChangeListeners.getInstance().registerTaskStackListener( 646 mTaskStackListener); 647 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, 648 mMainExecutor::execute, mOnPropertiesChangedListener); 649 mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener( 650 mOnIsInPipStateChangedListener)); 651 mDesktopModeOptional.ifPresent( 652 dm -> dm.addDesktopGestureExclusionRegionListener( 653 mDesktopCornersChangedListener, mMainExecutor)); 654 655 try { 656 mWindowManagerService.registerSystemGestureExclusionListener( 657 mGestureExclusionListener, mDisplayId); 658 } catch (RemoteException | IllegalArgumentException e) { 659 Log.e(TAG, "Failed to register window manager callbacks", e); 660 } 661 662 // Register input event receiver 663 mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput( 664 "edge-swipe", mDisplayId); 665 mInputEventReceiver = new InputChannelCompat.InputEventReceiver( 666 mInputMonitor.getInputChannel(), Looper.getMainLooper(), 667 Choreographer.getInstance(), this::onInputEvent); 668 669 // Add a nav bar panel window 670 mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE); 671 resetEdgeBackPlugin(); 672 mPluginManager.addPluginListener( 673 this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); 674 } 675 // Update the ML model resources. 676 updateMLModelState(); 677 } finally { 678 Trace.endSection(); 679 } 680 } 681 682 @Override onPluginConnected(NavigationEdgeBackPlugin plugin, Context context)683 public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) { 684 setEdgeBackPlugin(plugin); 685 } 686 687 @Override onPluginDisconnected(NavigationEdgeBackPlugin plugin)688 public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { 689 resetEdgeBackPlugin(); 690 } 691 resetEdgeBackPlugin()692 private void resetEdgeBackPlugin() { 693 if (mIsNewBackAffordanceEnabled) { 694 setEdgeBackPlugin( 695 mBackPanelControllerFactory.create(mContext)); 696 } else { 697 setEdgeBackPlugin(mNavBarEdgePanelProvider.get()); 698 } 699 } 700 setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin)701 private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { 702 try { 703 Trace.beginSection("setEdgeBackPlugin"); 704 mEdgeBackPlugin = edgeBackPlugin; 705 mEdgeBackPlugin.setBackCallback(mBackCallback); 706 mEdgeBackPlugin.setLayoutParams(createLayoutParams()); 707 updateDisplaySize(); 708 } finally { 709 Trace.endSection(); 710 } 711 } 712 isHandlingGestures()713 public boolean isHandlingGestures() { 714 return mIsEnabled && mIsBackGestureAllowed; 715 } 716 isButtonForcedVisible()717 public boolean isButtonForcedVisible() { 718 return mIsButtonForcedVisible; 719 } 720 721 /** 722 * Update the PiP bounds, used for exclusion calculation. 723 */ setPipStashExclusionBounds(Rect bounds)724 public void setPipStashExclusionBounds(Rect bounds) { 725 mPipExcludedBounds.set(bounds); 726 } 727 createLayoutParams()728 private WindowManager.LayoutParams createLayoutParams() { 729 Resources resources = mContext.getResources(); 730 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( 731 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width), 732 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height), 733 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 734 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 735 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 736 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, 737 PixelFormat.TRANSLUCENT); 738 layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel); 739 layoutParams.windowAnimations = 0; 740 layoutParams.privateFlags |= 741 (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS 742 | PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION); 743 layoutParams.setTitle(TAG + mContext.getDisplayId()); 744 layoutParams.setFitInsetsTypes(0 /* types */); 745 layoutParams.setTrustedOverlay(); 746 return layoutParams; 747 } 748 onInputEvent(InputEvent ev)749 private void onInputEvent(InputEvent ev) { 750 if (!(ev instanceof MotionEvent)) return; 751 MotionEvent event = (MotionEvent) ev; 752 onMotionEvent(event); 753 } 754 updateMLModelState()755 private void updateMLModelState() { 756 boolean newState = mIsGestureHandlingEnabled && DeviceConfig.getBoolean( 757 DeviceConfig.NAMESPACE_SYSTEMUI, 758 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false); 759 760 if (newState == mUseMLModel) { 761 return; 762 } 763 764 mUseMLModel = newState; 765 766 if (mUseMLModel) { 767 Assert.isMainThread(); 768 if (mMLModelIsLoading) { 769 Log.d(TAG, "Model tried to load while already loading."); 770 return; 771 } 772 mMLModelIsLoading = true; 773 mBackgroundExecutor.execute(() -> loadMLModel()); 774 } else if (mBackGestureTfClassifierProvider != null) { 775 mBackGestureTfClassifierProvider.release(); 776 mBackGestureTfClassifierProvider = null; 777 mVocab = null; 778 } 779 } 780 loadMLModel()781 private void loadMLModel() { 782 BackGestureTfClassifierProvider provider = mBackGestureTfClassifierProviderProvider.get(); 783 float threshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, 784 SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f); 785 Map<String, Integer> vocab = null; 786 if (provider != null && !provider.isActive()) { 787 provider.release(); 788 provider = null; 789 Log.w(TAG, "Cannot load model because it isn't active"); 790 } 791 if (provider != null) { 792 Trace.beginSection("EdgeBackGestureHandler#loadVocab"); 793 vocab = provider.loadVocab(mContext.getAssets()); 794 Trace.endSection(); 795 } 796 BackGestureTfClassifierProvider finalProvider = provider; 797 Map<String, Integer> finalVocab = vocab; 798 mMainExecutor.execute(() -> onMLModelLoadFinished(finalProvider, finalVocab, threshold)); 799 } 800 onMLModelLoadFinished(BackGestureTfClassifierProvider provider, Map<String, Integer> vocab, float threshold)801 private void onMLModelLoadFinished(BackGestureTfClassifierProvider provider, 802 Map<String, Integer> vocab, float threshold) { 803 Assert.isMainThread(); 804 mMLModelIsLoading = false; 805 if (!mUseMLModel) { 806 // This can happen if the user disables Gesture Nav while the model is loading. 807 if (provider != null) { 808 provider.release(); 809 } 810 Log.d(TAG, "Model finished loading but isn't needed."); 811 return; 812 } 813 mBackGestureTfClassifierProvider = provider; 814 mVocab = vocab; 815 mMLModelThreshold = threshold; 816 } 817 getBackGesturePredictionsCategory(int x, int y, int app)818 private int getBackGesturePredictionsCategory(int x, int y, int app) { 819 BackGestureTfClassifierProvider provider = mBackGestureTfClassifierProvider; 820 if (provider == null || app == -1) { 821 return -1; 822 } 823 int distanceFromEdge; 824 int location; 825 if (x <= mDisplaySize.x / 2.0) { 826 location = 1; // left 827 distanceFromEdge = x; 828 } else { 829 location = 2; // right 830 distanceFromEdge = mDisplaySize.x - x; 831 } 832 833 Object[] featuresVector = { 834 new long[]{(long) mDisplaySize.x}, 835 new long[]{(long) distanceFromEdge}, 836 new long[]{(long) location}, 837 new long[]{(long) app}, 838 new long[]{(long) y}, 839 }; 840 841 mMLResults = provider.predict(featuresVector); 842 if (mMLResults == -1) { 843 return -1; 844 } 845 return mMLResults >= mMLModelThreshold ? 1 : 0; 846 } 847 isWithinInsets(int x, int y)848 private boolean isWithinInsets(int x, int y) { 849 // Disallow if we are in the bottom gesture area 850 if (y >= (mDisplaySize.y - mBottomGestureHeight)) { 851 return false; 852 } 853 // If the point is way too far (twice the margin), it is 854 // not interesting to us for logging purposes, nor we 855 // should process it. Simply return false and keep 856 // mLogGesture = false. 857 if (x > 2 * (mEdgeWidthLeft + mLeftInset) 858 && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) { 859 return false; 860 } 861 return true; 862 } 863 isValidTrackpadBackGesture(boolean isTrackpadEvent)864 private boolean isValidTrackpadBackGesture(boolean isTrackpadEvent) { 865 if (!isTrackpadEvent) { 866 return false; 867 } 868 // for trackpad gestures, unless the whole screen is excluded region, 3-finger swipe 869 // gestures are allowed even if the cursor is in the excluded region. 870 WindowInsets windowInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets(); 871 Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars()); 872 final Rect excludeBounds = mExcludeRegion.getBounds(); 873 return !excludeBounds.contains(insets.left, insets.top, mDisplaySize.x - insets.right, 874 mDisplaySize.y - insets.bottom); 875 } 876 isTrackpadDevice(int deviceId)877 private boolean isTrackpadDevice(int deviceId) { 878 InputDevice inputDevice = mInputManager.getInputDevice(deviceId); 879 if (inputDevice == null) { 880 return false; 881 } 882 return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE 883 | InputDevice.SOURCE_TOUCHPAD); 884 } 885 desktopExcludeRegionContains(int x, int y)886 private boolean desktopExcludeRegionContains(int x, int y) { 887 return mDesktopModeExcludeRegion.contains(x, y); 888 } 889 isWithinTouchRegion(int x, int y)890 private boolean isWithinTouchRegion(int x, int y) { 891 // If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back 892 // gesture 893 final boolean isInsidePip = mIsInPip && mPipExcludedBounds.contains(x, y); 894 final boolean isInDesktopExcludeRegion = desktopExcludeRegionContains(x, y); 895 if (isInsidePip || isInDesktopExcludeRegion 896 || mNavBarOverlayExcludedBounds.contains(x, y)) { 897 return false; 898 } 899 900 int app = -1; 901 if (mVocab != null) { 902 app = mVocab.getOrDefault(mPackageName, -1); 903 } 904 905 // Denotes whether we should proceed with the gesture. Even if it is false, we may want to 906 // log it assuming it is not invalid due to exclusion. 907 boolean withinRange = x < mEdgeWidthLeft + mLeftInset 908 || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset); 909 if (withinRange) { 910 int results = -1; 911 912 // Check if we are within the tightest bounds beyond which we would not need to run the 913 // ML model 914 boolean withinMinRange = x < mMLEnableWidth + mLeftInset 915 || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset); 916 if (!withinMinRange && mUseMLModel && !mMLModelIsLoading 917 && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) { 918 withinRange = (results == 1); 919 } 920 } 921 922 // For debugging purposes 923 mPredictionLog.log(String.format("Prediction [%d,%d,%d,%d,%f,%d]", 924 System.currentTimeMillis(), x, y, app, mMLResults, withinRange ? 1 : 0)); 925 926 // Always allow if the user is in a transient sticky immersive state 927 if (mIsNavBarShownTransiently) { 928 mLogGesture = true; 929 return withinRange; 930 } 931 932 if (mExcludeRegion.contains(x, y)) { 933 if (withinRange) { 934 // We don't have the end point for logging purposes. 935 mEndPoint.x = -1; 936 mEndPoint.y = -1; 937 mLogGesture = true; 938 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED); 939 } 940 return false; 941 } 942 943 mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y); 944 mLogGesture = true; 945 return withinRange; 946 } 947 cancelGesture(MotionEvent ev)948 private void cancelGesture(MotionEvent ev) { 949 // Send action cancel to reset all the touch events 950 mAllowGesture = false; 951 mLogGesture = false; 952 mInRejectedExclusion = false; 953 MotionEvent cancelEv = MotionEvent.obtain(ev); 954 cancelEv.setAction(MotionEvent.ACTION_CANCEL); 955 mEdgeBackPlugin.onMotionEvent(cancelEv); 956 dispatchToBackAnimation(cancelEv); 957 cancelEv.recycle(); 958 } 959 logGesture(int backType)960 private void logGesture(int backType) { 961 if (!mLogGesture) { 962 return; 963 } 964 mLogGesture = false; 965 String logPackageName = ""; 966 Map<String, Integer> vocab = mVocab; 967 // Due to privacy, only top 100 most used apps by all users can be logged. 968 if (mUseMLModel && vocab != null && vocab.containsKey(mPackageName) 969 && vocab.get(mPackageName) < 100) { 970 logPackageName = mPackageName; 971 } 972 SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType, 973 (int) mDownPoint.y, mIsOnLeftEdge 974 ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT 975 : SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT, 976 (int) mDownPoint.x, (int) mDownPoint.y, 977 (int) mEndPoint.x, (int) mEndPoint.y, 978 mEdgeWidthLeft + mLeftInset, 979 mDisplaySize.x - (mEdgeWidthRight + mRightInset), 980 mUseMLModel ? mMLResults : -2, logPackageName, 981 mIsTrackpadThreeFingerSwipe ? SysUiStatsLog.BACK_GESTURE__INPUT_TYPE__TRACKPAD 982 : SysUiStatsLog.BACK_GESTURE__INPUT_TYPE__TOUCH); 983 } 984 onMotionEvent(MotionEvent ev)985 private void onMotionEvent(MotionEvent ev) { 986 int action = ev.getActionMasked(); 987 if (action == MotionEvent.ACTION_DOWN) { 988 if (DEBUG_MISSING_GESTURE) { 989 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev); 990 } 991 992 mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe( 993 mIsTrackpadGestureFeaturesEnabled, ev); 994 995 // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new 996 // ACTION_DOWN, in that case we should just reuse the old instance. 997 mVelocityTracker.clear(); 998 999 // Verify if this is in within the touch region and we aren't in immersive mode, and 1000 // either the bouncer is showing or the notification panel is hidden 1001 mInputEventReceiver.setBatchingEnabled(false); 1002 if (mIsTrackpadThreeFingerSwipe) { 1003 // Since trackpad gestures don't have zones, this will be determined later by the 1004 // direction of the gesture. {@code mIsOnLeftEdge} is set to false to begin with. 1005 mDeferSetIsOnLeftEdge = true; 1006 mIsOnLeftEdge = false; 1007 } else { 1008 mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; 1009 } 1010 mMLResults = 0; 1011 mLogGesture = false; 1012 mInRejectedExclusion = false; 1013 boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); 1014 boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed 1015 && !mGestureBlockingActivityRunning 1016 && !QuickStepContract.isBackGestureDisabled(mSysUiFlags, 1017 mIsTrackpadThreeFingerSwipe) 1018 && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev); 1019 if (mIsTrackpadThreeFingerSwipe) { 1020 // Trackpad back gestures don't have zones, so we don't need to check if the down 1021 // event is within insets. 1022 mAllowGesture = isBackAllowedCommon && isValidTrackpadBackGesture( 1023 true /* isTrackpadEvent */); 1024 } else { 1025 mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets 1026 && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()) 1027 && !isButtonPressFromTrackpad(ev); 1028 } 1029 if (mAllowGesture) { 1030 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); 1031 mEdgeBackPlugin.onMotionEvent(ev); 1032 dispatchToBackAnimation(ev); 1033 } 1034 if (mLogGesture || mIsTrackpadThreeFingerSwipe) { 1035 mDownPoint.set(ev.getX(), ev.getY()); 1036 mEndPoint.set(-1, -1); 1037 mThresholdCrossed = false; 1038 } 1039 1040 // For debugging purposes, only log edge points 1041 long curTime = System.currentTimeMillis(); 1042 mTmpLogDate.setTime(curTime); 1043 String curTimeStr = mLogDateFormat.format(mTmpLogDate); 1044 (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format( 1045 "Gesture [%d [%s],alw=%B, t3fs=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B," 1046 + " qsDisbld=%b, blkdAct=%B, pip=%B," 1047 + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]", 1048 curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe, 1049 mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed, 1050 QuickStepContract.isBackGestureDisabled(mSysUiFlags, 1051 mIsTrackpadThreeFingerSwipe), 1052 mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, 1053 mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); 1054 } else if (mAllowGesture || mLogGesture) { 1055 if (!mThresholdCrossed) { 1056 mEndPoint.x = (int) ev.getX(); 1057 mEndPoint.y = (int) ev.getY(); 1058 if (action == MotionEvent.ACTION_POINTER_DOWN && !mIsTrackpadThreeFingerSwipe) { 1059 if (mAllowGesture) { 1060 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH); 1061 if (DEBUG_MISSING_GESTURE) { 1062 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back: multitouch"); 1063 } 1064 // We do not support multi touch for back gesture 1065 cancelGesture(ev); 1066 } 1067 mLogGesture = false; 1068 return; 1069 } else if (action == MotionEvent.ACTION_MOVE) { 1070 if (mIsTrackpadThreeFingerSwipe && mDeferSetIsOnLeftEdge) { 1071 // mIsOnLeftEdge is determined by the relative position between the down 1072 // and the current motion event for trackpad gestures instead of zoning. 1073 mIsOnLeftEdge = mEndPoint.x > mDownPoint.x; 1074 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); 1075 mDeferSetIsOnLeftEdge = false; 1076 } 1077 1078 if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) { 1079 if (mAllowGesture) { 1080 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS); 1081 cancelGesture(ev); 1082 if (DEBUG_MISSING_GESTURE) { 1083 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back [longpress]: " 1084 + ev.getEventTime() 1085 + " " + ev.getDownTime() 1086 + " " + mLongPressTimeout); 1087 } 1088 } 1089 mLogGesture = false; 1090 return; 1091 } 1092 float dx = Math.abs(ev.getX() - mDownPoint.x); 1093 float dy = Math.abs(ev.getY() - mDownPoint.y); 1094 if (dy > dx && dy > mTouchSlop) { 1095 if (mAllowGesture) { 1096 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE); 1097 cancelGesture(ev); 1098 if (DEBUG_MISSING_GESTURE) { 1099 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back [vertical move]: " 1100 + dy + " " + dx + " " + mTouchSlop); 1101 } 1102 } 1103 mLogGesture = false; 1104 return; 1105 } else if (dx > dy && dx > mTouchSlop) { 1106 if (mAllowGesture) { 1107 mThresholdCrossed = true; 1108 // Capture inputs 1109 mInputMonitor.pilferPointers(); 1110 if (mBackAnimation != null) { 1111 // Notify FalsingManager that an intentional gesture has occurred. 1112 mFalsingManager.isFalseTouch(BACK_GESTURE); 1113 } 1114 mInputEventReceiver.setBatchingEnabled(true); 1115 } else { 1116 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE); 1117 } 1118 } 1119 } 1120 } 1121 1122 if (mAllowGesture) { 1123 // forward touch 1124 mEdgeBackPlugin.onMotionEvent(ev); 1125 dispatchToBackAnimation(ev); 1126 } 1127 } 1128 } 1129 isButtonPressFromTrackpad(MotionEvent ev)1130 private boolean isButtonPressFromTrackpad(MotionEvent ev) { 1131 // We don't allow back for button press from the trackpad, and yet we do with a mouse. 1132 int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources(); 1133 int sourceTrackpad = (SOURCE_MOUSE | SOURCE_TOUCHPAD); 1134 return (sources & sourceTrackpad) == sourceTrackpad && ev.getButtonState() != 0; 1135 } 1136 dispatchToBackAnimation(MotionEvent event)1137 private void dispatchToBackAnimation(MotionEvent event) { 1138 if (mBackAnimation != null) { 1139 mVelocityTracker.addMovement(event); 1140 1141 final float velocityX; 1142 final float velocityY; 1143 if (event.getAction() == MotionEvent.ACTION_UP) { 1144 // Compute the current velocity is expensive (see computeCurrentVelocity), so we 1145 // are only doing it when the user completes the gesture. 1146 int unitPixelPerSecond = 1000; 1147 int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity(); 1148 mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity); 1149 velocityX = mVelocityTracker.getXVelocity(); 1150 velocityY = mVelocityTracker.getYVelocity(); 1151 } else { 1152 velocityX = Float.NaN; 1153 velocityY = Float.NaN; 1154 } 1155 1156 mBackAnimation.onBackMotion( 1157 /* touchX = */ event.getX(), 1158 /* touchY = */ event.getY(), 1159 /* velocityX = */ velocityX, 1160 /* velocityY = */ velocityY, 1161 /* keyAction = */ event.getActionMasked(), 1162 /* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); 1163 } 1164 } 1165 updateDisabledForQuickstep(Configuration newConfig)1166 private void updateDisabledForQuickstep(Configuration newConfig) { 1167 int rotation = newConfig.windowConfiguration.getRotation(); 1168 mDisabledForQuickstep = mStartingQuickstepRotation > -1 && 1169 mStartingQuickstepRotation != rotation; 1170 } 1171 onConfigurationChanged(@onNull Configuration newConfig)1172 public void onConfigurationChanged(@NonNull Configuration newConfig) { 1173 if (mStartingQuickstepRotation > -1) { 1174 updateDisabledForQuickstep(newConfig); 1175 } 1176 1177 // TODO(b/243765256): Disable this logging once b/243765256 is fixed. 1178 Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig 1179 + " lastReportedConfig=" + mLastReportedConfig); 1180 mLastReportedConfig.updateFrom(newConfig); 1181 updateDisplaySize(); 1182 } 1183 updateDisplaySize()1184 private void updateDisplaySize() { 1185 Rect bounds = mLastReportedConfig.windowConfiguration.getMaxBounds(); 1186 mDisplaySize.set(bounds.width(), bounds.height()); 1187 if (DEBUG_MISSING_GESTURE) { 1188 Log.d(DEBUG_MISSING_GESTURE_TAG, "Update display size: mDisplaySize=" + mDisplaySize); 1189 } 1190 1191 if (mEdgeBackPlugin != null) { 1192 mEdgeBackPlugin.setDisplaySize(mDisplaySize); 1193 } 1194 updateBackAnimationThresholds(); 1195 } 1196 updateBackAnimationThresholds()1197 private void updateBackAnimationThresholds() { 1198 if (mBackAnimation == null) { 1199 return; 1200 } 1201 int maxDistance = mDisplaySize.x; 1202 float linearDistance = Math.min(maxDistance, mBackSwipeLinearThreshold); 1203 mBackAnimation.setSwipeThresholds(linearDistance, maxDistance, mNonLinearFactor); 1204 } 1205 sendEvent(int action, int code)1206 private boolean sendEvent(int action, int code) { 1207 long when = SystemClock.uptimeMillis(); 1208 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 1209 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 1210 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 1211 InputDevice.SOURCE_KEYBOARD); 1212 1213 ev.setDisplayId(mContext.getDisplay().getDisplayId()); 1214 return mContext.getSystemService(InputManager.class) 1215 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 1216 } 1217 setInsets(int leftInset, int rightInset)1218 public void setInsets(int leftInset, int rightInset) { 1219 mLeftInset = leftInset; 1220 mRightInset = rightInset; 1221 if (mEdgeBackPlugin != null) { 1222 mEdgeBackPlugin.setInsets(leftInset, rightInset); 1223 } 1224 } 1225 dump(PrintWriter pw)1226 public void dump(PrintWriter pw) { 1227 pw.println("EdgeBackGestureHandler:"); 1228 pw.println(" mIsEnabled=" + mIsEnabled); 1229 pw.println(" mIsAttached=" + mIsAttached); 1230 pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); 1231 pw.println(" mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled); 1232 pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); 1233 pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning); 1234 pw.println(" mAllowGesture=" + mAllowGesture); 1235 pw.println(" mUseMLModel=" + mUseMLModel); 1236 pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); 1237 pw.println(" mStartingQuickstepRotation=" + mStartingQuickstepRotation); 1238 pw.println(" mInRejectedExclusion=" + mInRejectedExclusion); 1239 pw.println(" mExcludeRegion=" + mExcludeRegion); 1240 pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); 1241 pw.println(" mIsInPip=" + mIsInPip); 1242 pw.println(" mPipExcludedBounds=" + mPipExcludedBounds); 1243 pw.println(" mDesktopModeExclusionRegion=" + mDesktopModeExcludeRegion); 1244 pw.println(" mNavBarOverlayExcludedBounds=" + mNavBarOverlayExcludedBounds); 1245 pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); 1246 pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); 1247 pw.println(" mLeftInset=" + mLeftInset); 1248 pw.println(" mRightInset=" + mRightInset); 1249 pw.println(" mMLEnableWidth=" + mMLEnableWidth); 1250 pw.println(" mMLModelThreshold=" + mMLModelThreshold); 1251 pw.println(" mTouchSlop=" + mTouchSlop); 1252 pw.println(" mBottomGestureHeight=" + mBottomGestureHeight); 1253 pw.println(" mPredictionLog=" + String.join("\n", mPredictionLog)); 1254 pw.println(" mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets)); 1255 pw.println(" mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets)); 1256 pw.println(" mIsTrackpadConnected=" + mIsTrackpadConnected); 1257 pw.println(" mUsingThreeButtonNav=" + mUsingThreeButtonNav); 1258 pw.println(" mEdgeBackPlugin=" + mEdgeBackPlugin); 1259 if (mEdgeBackPlugin != null) { 1260 mEdgeBackPlugin.dump(pw); 1261 } 1262 } 1263 isGestureBlockingActivityRunning()1264 private boolean isGestureBlockingActivityRunning() { 1265 ActivityManager.RunningTaskInfo runningTask = 1266 ActivityManagerWrapper.getInstance().getRunningTask(); 1267 ComponentName topActivity = runningTask == null ? null : runningTask.topActivity; 1268 if (topActivity != null) { 1269 mPackageName = topActivity.getPackageName(); 1270 } else { 1271 mPackageName = "_UNKNOWN"; 1272 } 1273 return topActivity != null && mGestureBlockingActivities.contains(topActivity); 1274 } 1275 setBackAnimation(BackAnimation backAnimation)1276 public void setBackAnimation(BackAnimation backAnimation) { 1277 mBackAnimation = backAnimation; 1278 updateBackAnimationThresholds(); 1279 if (mLightBarControllerProvider.get() != null) { 1280 mBackAnimation.setStatusBarCustomizer((appearance) -> { 1281 mMainExecutor.execute(() -> 1282 mLightBarControllerProvider.get() 1283 .customizeStatusBarAppearance(appearance)); 1284 }); 1285 } 1286 } 1287 1288 /** 1289 * Injectable instance to create a new EdgeBackGestureHandler. 1290 * 1291 * Necessary because we don't have good handling of per-display contexts at the moment. With 1292 * this, you can pass in a specific context that knows what display it is in. 1293 */ 1294 public static class Factory { 1295 private final OverviewProxyService mOverviewProxyService; 1296 private final SysUiState mSysUiState; 1297 private final PluginManager mPluginManager; 1298 private final Executor mExecutor; 1299 private final Handler mHandler; 1300 private final Executor mBackgroundExecutor; 1301 private final UserTracker mUserTracker; 1302 private final NavigationModeController mNavigationModeController; 1303 private final BackPanelController.Factory mBackPanelControllerFactory; 1304 private final ViewConfiguration mViewConfiguration; 1305 private final WindowManager mWindowManager; 1306 private final IWindowManager mWindowManagerService; 1307 private final InputManager mInputManager; 1308 private final Optional<Pip> mPipOptional; 1309 private final Optional<DesktopMode> mDesktopModeOptional; 1310 private final FalsingManager mFalsingManager; 1311 private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider; 1312 private final Provider<BackGestureTfClassifierProvider> 1313 mBackGestureTfClassifierProviderProvider; 1314 private final FeatureFlags mFeatureFlags; 1315 private final Provider<LightBarController> mLightBarControllerProvider; 1316 1317 @Inject Factory(OverviewProxyService overviewProxyService, SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, @Main Handler handler, @Background Executor backgroundExecutor, UserTracker userTracker, NavigationModeController navigationModeController, BackPanelController.Factory backPanelControllerFactory, ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, InputManager inputManager, Optional<Pip> pipOptional, Optional<DesktopMode> desktopModeOptional, FalsingManager falsingManager, Provider<NavigationBarEdgePanel> navBarEdgePanelProvider, Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider, FeatureFlags featureFlags, Provider<LightBarController> lightBarControllerProvider)1318 public Factory(OverviewProxyService overviewProxyService, 1319 SysUiState sysUiState, 1320 PluginManager pluginManager, 1321 @Main Executor executor, 1322 @Main Handler handler, 1323 @Background Executor backgroundExecutor, 1324 UserTracker userTracker, 1325 NavigationModeController navigationModeController, 1326 BackPanelController.Factory backPanelControllerFactory, 1327 ViewConfiguration viewConfiguration, 1328 WindowManager windowManager, 1329 IWindowManager windowManagerService, 1330 InputManager inputManager, 1331 Optional<Pip> pipOptional, 1332 Optional<DesktopMode> desktopModeOptional, 1333 FalsingManager falsingManager, 1334 Provider<NavigationBarEdgePanel> navBarEdgePanelProvider, 1335 Provider<BackGestureTfClassifierProvider> 1336 backGestureTfClassifierProviderProvider, 1337 FeatureFlags featureFlags, 1338 Provider<LightBarController> lightBarControllerProvider) { 1339 mOverviewProxyService = overviewProxyService; 1340 mSysUiState = sysUiState; 1341 mPluginManager = pluginManager; 1342 mExecutor = executor; 1343 mHandler = handler; 1344 mBackgroundExecutor = backgroundExecutor; 1345 mUserTracker = userTracker; 1346 mNavigationModeController = navigationModeController; 1347 mBackPanelControllerFactory = backPanelControllerFactory; 1348 mViewConfiguration = viewConfiguration; 1349 mWindowManager = windowManager; 1350 mWindowManagerService = windowManagerService; 1351 mInputManager = inputManager; 1352 mPipOptional = pipOptional; 1353 mDesktopModeOptional = desktopModeOptional; 1354 mFalsingManager = falsingManager; 1355 mNavBarEdgePanelProvider = navBarEdgePanelProvider; 1356 mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider; 1357 mFeatureFlags = featureFlags; 1358 mLightBarControllerProvider = lightBarControllerProvider; 1359 } 1360 1361 /** Construct a {@link EdgeBackGestureHandler}. */ create(Context context)1362 public EdgeBackGestureHandler create(Context context) { 1363 return new EdgeBackGestureHandler( 1364 context, 1365 mOverviewProxyService, 1366 mSysUiState, 1367 mPluginManager, 1368 mExecutor, 1369 mHandler, 1370 mBackgroundExecutor, 1371 mUserTracker, 1372 mNavigationModeController, 1373 mBackPanelControllerFactory, 1374 mViewConfiguration, 1375 mWindowManager, 1376 mWindowManagerService, 1377 mInputManager, 1378 mPipOptional, 1379 mDesktopModeOptional, 1380 mFalsingManager, 1381 mNavBarEdgePanelProvider, 1382 mBackGestureTfClassifierProviderProvider, 1383 mFeatureFlags, 1384 mLightBarControllerProvider); 1385 } 1386 } 1387 1388 private static class LogArray extends ArrayDeque<String> { 1389 private final int mLength; 1390 LogArray(int length)1391 LogArray(int length) { 1392 mLength = length; 1393 } 1394 log(String message)1395 void log(String message) { 1396 if (size() >= mLength) { 1397 removeFirst(); 1398 } 1399 addLast(message); 1400 if (DEBUG_MISSING_GESTURE) { 1401 Log.d(DEBUG_MISSING_GESTURE_TAG, message); 1402 } 1403 } 1404 } 1405 } 1406