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