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