• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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