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