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