• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2019 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.statusbar.phone;
17 
18 import static android.view.Display.INVALID_DISPLAY;
19 
20 import android.app.ActivityManager;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.content.res.Resources;
26 import android.graphics.PixelFormat;
27 import android.graphics.Point;
28 import android.graphics.PointF;
29 import android.graphics.Region;
30 import android.hardware.display.DisplayManager;
31 import android.hardware.display.DisplayManager.DisplayListener;
32 import android.hardware.input.InputManager;
33 import android.os.Looper;
34 import android.os.RemoteException;
35 import android.os.SystemClock;
36 import android.os.SystemProperties;
37 import android.provider.DeviceConfig;
38 import android.util.DisplayMetrics;
39 import android.util.Log;
40 import android.util.TypedValue;
41 import android.view.ISystemGestureExclusionListener;
42 import android.view.InputChannel;
43 import android.view.InputDevice;
44 import android.view.InputEvent;
45 import android.view.InputEventReceiver;
46 import android.view.InputMonitor;
47 import android.view.KeyCharacterMap;
48 import android.view.KeyEvent;
49 import android.view.MotionEvent;
50 import android.view.Surface;
51 import android.view.ViewConfiguration;
52 import android.view.WindowManager;
53 import android.view.WindowManagerGlobal;
54 
55 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
56 import com.android.internal.policy.GestureNavigationSettingsObserver;
57 import com.android.systemui.Dependency;
58 import com.android.systemui.R;
59 import com.android.systemui.SystemUIFactory;
60 import com.android.systemui.broadcast.BroadcastDispatcher;
61 import com.android.systemui.bubbles.BubbleController;
62 import com.android.systemui.model.SysUiState;
63 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
64 import com.android.systemui.plugins.PluginListener;
65 import com.android.systemui.recents.OverviewProxyService;
66 import com.android.systemui.settings.CurrentUserTracker;
67 import com.android.systemui.shared.plugins.PluginManager;
68 import com.android.systemui.shared.system.ActivityManagerWrapper;
69 import com.android.systemui.shared.system.QuickStepContract;
70 import com.android.systemui.shared.system.SysUiStatsLog;
71 import com.android.systemui.shared.system.TaskStackChangeListener;
72 import com.android.systemui.shared.tracing.ProtoTraceable;
73 import com.android.systemui.tracing.ProtoTracer;
74 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
75 import com.android.systemui.tracing.nano.SystemUiTraceProto;
76 
77 import java.io.PrintWriter;
78 import java.util.ArrayDeque;
79 import java.util.ArrayList;
80 import java.util.List;
81 import java.util.Map;
82 import java.util.concurrent.Executor;
83 
84 /**
85  * Utility class to handle edge swipes for back gesture
86  */
87 public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener,
88         PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
89 
90     private static final String TAG = "EdgeBackGestureHandler";
91     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
92             "gestures.back_timeout", 250);
93 
94     private ISystemGestureExclusionListener mGestureExclusionListener =
95             new ISystemGestureExclusionListener.Stub() {
96                 @Override
97                 public void onSystemGestureExclusionChanged(int displayId,
98                         Region systemGestureExclusion, Region unrestrictedOrNull) {
99                     if (displayId == mDisplayId) {
100                         mMainExecutor.execute(() -> {
101                             mExcludeRegion.set(systemGestureExclusion);
102                             mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null
103                                     ? unrestrictedOrNull : systemGestureExclusion);
104                         });
105                     }
106                 }
107             };
108 
109     private OverviewProxyService.OverviewProxyListener mQuickSwitchListener =
110             new OverviewProxyService.OverviewProxyListener() {
111                 @Override
112                 public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
113                     mStartingQuickstepRotation = rotation;
114                     updateDisabledForQuickstep();
115                 }
116             };
117 
118     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
119         @Override
120         public void onTaskStackChanged() {
121             mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
122         }
123         @Override
124         public void onTaskCreated(int taskId, ComponentName componentName) {
125             if (componentName != null) {
126                 mPackageName = componentName.getPackageName();
127             } else {
128                 mPackageName = "_UNKNOWN";
129             }
130         }
131     };
132 
133     private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
134             new DeviceConfig.OnPropertiesChangedListener() {
135                 @Override
136                 public void onPropertiesChanged(DeviceConfig.Properties properties) {
137                     if (DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())
138                             && (properties.getKeyset().contains(
139                                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD)
140                             || properties.getKeyset().contains(
141                                     SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL)
142                             || properties.getKeyset().contains(
143                                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME))) {
144                         updateMLModelState();
145                     }
146                 }
147             };
148 
149 
150     private final Context mContext;
151     private final OverviewProxyService mOverviewProxyService;
152     private final Runnable mStateChangeCallback;
153 
154     private final PluginManager mPluginManager;
155     // Activities which should not trigger Back gesture.
156     private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
157 
158     private final Point mDisplaySize = new Point();
159     private final int mDisplayId;
160 
161     private final Executor mMainExecutor;
162 
163     private final Region mExcludeRegion = new Region();
164     private final Region mUnrestrictedExcludeRegion = new Region();
165 
166     // The left side edge width where touch down is allowed
167     private int mEdgeWidthLeft;
168     // The right side edge width where touch down is allowed
169     private int mEdgeWidthRight;
170     // The bottom gesture area height
171     private float mBottomGestureHeight;
172     // The slop to distinguish between horizontal and vertical motion
173     private float mTouchSlop;
174     // Duration after which we consider the event as longpress.
175     private final int mLongPressTimeout;
176     private int mStartingQuickstepRotation = -1;
177     // We temporarily disable back gesture when user is quickswitching
178     // between apps of different orientations
179     private boolean mDisabledForQuickstep;
180 
181     private final PointF mDownPoint = new PointF();
182     private final PointF mEndPoint = new PointF();
183     private boolean mThresholdCrossed = false;
184     private boolean mAllowGesture = false;
185     private boolean mLogGesture = false;
186     private boolean mInRejectedExclusion = false;
187     private boolean mIsOnLeftEdge;
188 
189     private boolean mIsAttached;
190     private boolean mIsGesturalModeEnabled;
191     private boolean mIsEnabled;
192     private boolean mIsNavBarShownTransiently;
193     private boolean mIsBackGestureAllowed;
194     private boolean mGestureBlockingActivityRunning;
195 
196     private InputMonitor mInputMonitor;
197     private InputEventReceiver mInputEventReceiver;
198 
199     private NavigationEdgeBackPlugin mEdgeBackPlugin;
200     private int mLeftInset;
201     private int mRightInset;
202     private int mSysUiFlags;
203 
204     // For Tf-Lite model.
205     private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
206     private Map<String, Integer> mVocab;
207     private boolean mUseMLModel;
208     // minimum width below which we do not run the model
209     private int mMLEnableWidth;
210     private float mMLModelThreshold;
211     private String mPackageName;
212     private float mMLResults;
213 
214     private static final int MAX_LOGGED_PREDICTIONS = 10;
215     private ArrayDeque<String> mPredictionLog = new ArrayDeque<>();
216 
217     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
218 
219     private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
220             new NavigationEdgeBackPlugin.BackCallback() {
221                 @Override
222                 public void triggerBack() {
223                     sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
224                     sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
225 
226                     mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
227                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
228                     logGesture(mInRejectedExclusion
229                             ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
230                             : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
231                 }
232 
233                 @Override
234                 public void cancelBack() {
235                     logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
236                     mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
237                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
238                 }
239             };
240 
EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiFlagContainer, PluginManager pluginManager, Runnable stateChangeCallback)241     public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
242             SysUiState sysUiFlagContainer, PluginManager pluginManager,
243             Runnable stateChangeCallback) {
244         super(Dependency.get(BroadcastDispatcher.class));
245         mContext = context;
246         mDisplayId = context.getDisplayId();
247         mMainExecutor = context.getMainExecutor();
248         mOverviewProxyService = overviewProxyService;
249         mPluginManager = pluginManager;
250         mStateChangeCallback = stateChangeCallback;
251         ComponentName recentsComponentName = ComponentName.unflattenFromString(
252                 context.getString(com.android.internal.R.string.config_recentsComponentName));
253         if (recentsComponentName != null) {
254             String recentsPackageName = recentsComponentName.getPackageName();
255             PackageManager manager = context.getPackageManager();
256             try {
257                 Resources resources = manager.getResourcesForApplication(recentsPackageName);
258                 int resId = resources.getIdentifier(
259                         "gesture_blocking_activities", "array", recentsPackageName);
260 
261                 if (resId == 0) {
262                     Log.e(TAG, "No resource found for gesture-blocking activities");
263                 } else {
264                     String[] gestureBlockingActivities = resources.getStringArray(resId);
265                     for (String gestureBlockingActivity : gestureBlockingActivities) {
266                         mGestureBlockingActivities.add(
267                                 ComponentName.unflattenFromString(gestureBlockingActivity));
268                     }
269                 }
270             } catch (NameNotFoundException e) {
271                 Log.e(TAG, "Failed to add gesture blocking activities", e);
272             }
273         }
274         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
275                 ViewConfiguration.getLongPressTimeout());
276 
277         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
278                 mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
279 
280         updateCurrentUserResources();
281         sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
282     }
283 
updateCurrentUserResources()284     public void updateCurrentUserResources() {
285         Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext()
286                 .getResources();
287         mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
288         mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
289         mIsBackGestureAllowed =
290                 !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
291 
292         final DisplayMetrics dm = res.getDisplayMetrics();
293         final float defaultGestureHeight = res.getDimension(
294                 com.android.internal.R.dimen.navigation_bar_gesture_height) / dm.density;
295         final float gestureHeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
296                 SystemUiDeviceConfigFlags.BACK_GESTURE_BOTTOM_HEIGHT,
297                 defaultGestureHeight);
298         mBottomGestureHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, gestureHeight,
299                 dm);
300 
301         // Set the minimum bounds to activate ML to 12dp or the minimum of configured values
302         mMLEnableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12.0f, dm);
303         if (mMLEnableWidth > mEdgeWidthRight) mMLEnableWidth = mEdgeWidthRight;
304         if (mMLEnableWidth > mEdgeWidthLeft) mMLEnableWidth = mEdgeWidthLeft;
305 
306         // Reduce the default touch slop to ensure that we can intercept the gesture
307         // before the app starts to react to it.
308         // TODO(b/130352502) Tune this value and extract into a constant
309         final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
310                         SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f);
311         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop() * backGestureSlop;
312     }
313 
onNavigationSettingsChanged()314     private void onNavigationSettingsChanged() {
315         boolean wasBackAllowed = isHandlingGestures();
316         updateCurrentUserResources();
317         if (wasBackAllowed != isHandlingGestures()) {
318             mStateChangeCallback.run();
319         }
320     }
321 
322     @Override
onUserSwitched(int newUserId)323     public void onUserSwitched(int newUserId) {
324         updateIsEnabled();
325         updateCurrentUserResources();
326     }
327 
328     /**
329      * @see NavigationBarView#onAttachedToWindow()
330      */
onNavBarAttached()331     public void onNavBarAttached() {
332         mIsAttached = true;
333         Dependency.get(ProtoTracer.class).add(this);
334         mOverviewProxyService.addCallback(mQuickSwitchListener);
335         updateIsEnabled();
336         startTracking();
337     }
338 
339     /**
340      * @see NavigationBarView#onDetachedFromWindow()
341      */
onNavBarDetached()342     public void onNavBarDetached() {
343         mIsAttached = false;
344         Dependency.get(ProtoTracer.class).remove(this);
345         mOverviewProxyService.removeCallback(mQuickSwitchListener);
346         updateIsEnabled();
347         stopTracking();
348     }
349 
350     /**
351      * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
352      */
onNavigationModeChanged(int mode)353     public void onNavigationModeChanged(int mode) {
354         mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
355         updateIsEnabled();
356         updateCurrentUserResources();
357     }
358 
onNavBarTransientStateChanged(boolean isTransient)359     public void onNavBarTransientStateChanged(boolean isTransient) {
360         mIsNavBarShownTransiently = isTransient;
361     }
362 
disposeInputChannel()363     private void disposeInputChannel() {
364         if (mInputEventReceiver != null) {
365             mInputEventReceiver.dispose();
366             mInputEventReceiver = null;
367         }
368         if (mInputMonitor != null) {
369             mInputMonitor.dispose();
370             mInputMonitor = null;
371         }
372     }
373 
updateIsEnabled()374     private void updateIsEnabled() {
375         boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
376         if (isEnabled == mIsEnabled) {
377             return;
378         }
379         mIsEnabled = isEnabled;
380         disposeInputChannel();
381 
382         if (mEdgeBackPlugin != null) {
383             mEdgeBackPlugin.onDestroy();
384             mEdgeBackPlugin = null;
385         }
386 
387         if (!mIsEnabled) {
388             mGestureNavigationSettingsObserver.unregister();
389             mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
390             mPluginManager.removePluginListener(this);
391             ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
392             DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
393 
394             try {
395                 WindowManagerGlobal.getWindowManagerService()
396                         .unregisterSystemGestureExclusionListener(
397                                 mGestureExclusionListener, mDisplayId);
398             } catch (RemoteException | IllegalArgumentException e) {
399                 Log.e(TAG, "Failed to unregister window manager callbacks", e);
400             }
401 
402         } else {
403             mGestureNavigationSettingsObserver.register();
404             updateDisplaySize();
405             mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
406                     mContext.getMainThreadHandler());
407             ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
408             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
409                     runnable -> (mContext.getMainThreadHandler()).post(runnable),
410                     mOnPropertiesChangedListener);
411 
412             try {
413                 WindowManagerGlobal.getWindowManagerService()
414                         .registerSystemGestureExclusionListener(
415                                 mGestureExclusionListener, mDisplayId);
416             } catch (RemoteException | IllegalArgumentException e) {
417                 Log.e(TAG, "Failed to register window manager callbacks", e);
418             }
419 
420             // Register input event receiver
421             mInputMonitor = InputManager.getInstance().monitorGestureInput(
422                     "edge-swipe", mDisplayId);
423             mInputEventReceiver = new SysUiInputEventReceiver(
424                     mInputMonitor.getInputChannel(), Looper.getMainLooper());
425 
426             // Add a nav bar panel window
427             setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
428             mPluginManager.addPluginListener(
429                     this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
430         }
431         // Update the ML model resources.
432         updateMLModelState();
433     }
434 
435     @Override
onPluginConnected(NavigationEdgeBackPlugin plugin, Context context)436     public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) {
437         setEdgeBackPlugin(plugin);
438     }
439 
440     @Override
onPluginDisconnected(NavigationEdgeBackPlugin plugin)441     public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) {
442         setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
443     }
444 
setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin)445     private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
446         if (mEdgeBackPlugin != null) {
447             mEdgeBackPlugin.onDestroy();
448         }
449         mEdgeBackPlugin = edgeBackPlugin;
450         mEdgeBackPlugin.setBackCallback(mBackCallback);
451         mEdgeBackPlugin.setLayoutParams(createLayoutParams());
452         updateDisplaySize();
453     }
454 
isHandlingGestures()455     public boolean isHandlingGestures() {
456         return mIsEnabled && mIsBackGestureAllowed;
457     }
458 
createLayoutParams()459     private WindowManager.LayoutParams createLayoutParams() {
460         Resources resources = mContext.getResources();
461         WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
462                 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
463                 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
464                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
465                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
466                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
467                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
468                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
469                 PixelFormat.TRANSLUCENT);
470         layoutParams.privateFlags |=
471                 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
472         layoutParams.setTitle(TAG + mContext.getDisplayId());
473         layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
474         layoutParams.windowAnimations = 0;
475         layoutParams.setFitInsetsTypes(0 /* types */);
476         return layoutParams;
477     }
478 
onInputEvent(InputEvent ev)479     private void onInputEvent(InputEvent ev) {
480         if (ev instanceof MotionEvent) {
481             onMotionEvent((MotionEvent) ev);
482         }
483     }
484 
updateMLModelState()485     private void updateMLModelState() {
486         boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
487                 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
488 
489         if (newState == mUseMLModel) {
490             return;
491         }
492 
493         if (newState) {
494             String mlModelName = DeviceConfig.getString(DeviceConfig.NAMESPACE_SYSTEMUI,
495                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME, "backgesture");
496             mBackGestureTfClassifierProvider = SystemUIFactory.getInstance()
497                     .createBackGestureTfClassifierProvider(mContext.getAssets(), mlModelName);
498             mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
499                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
500             if (mBackGestureTfClassifierProvider.isActive()) {
501                 mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets());
502                 mUseMLModel = true;
503                 return;
504             }
505         }
506 
507         mUseMLModel = false;
508         if (mBackGestureTfClassifierProvider != null) {
509             mBackGestureTfClassifierProvider.release();
510             mBackGestureTfClassifierProvider = null;
511         }
512     }
513 
getBackGesturePredictionsCategory(int x, int y, int app)514     private int getBackGesturePredictionsCategory(int x, int y, int app) {
515         if (app == -1) {
516             return -1;
517         }
518 
519         int distanceFromEdge;
520         int location;
521         if (x <= mDisplaySize.x / 2.0) {
522             location = 1;  // left
523             distanceFromEdge = x;
524         } else {
525             location = 2;  // right
526             distanceFromEdge = mDisplaySize.x - x;
527         }
528 
529         Object[] featuresVector = {
530             new long[]{(long) mDisplaySize.x},
531             new long[]{(long) distanceFromEdge},
532             new long[]{(long) location},
533             new long[]{(long) app},
534             new long[]{(long) y},
535         };
536 
537         mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector);
538         if (mMLResults == -1) {
539             return -1;
540         }
541 
542         return mMLResults >= mMLModelThreshold ? 1 : 0;
543     }
544 
isWithinTouchRegion(int x, int y)545     private boolean isWithinTouchRegion(int x, int y) {
546         // Disallow if we are in the bottom gesture area
547         if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
548             return false;
549         }
550         // If the point is way too far (twice the margin), it is
551         // not interesting to us for logging purposes, nor we
552         // should process it.  Simply return false and keep
553         // mLogGesture = false.
554         if (x > 2 * (mEdgeWidthLeft + mLeftInset)
555                 && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) {
556             return false;
557         }
558 
559         int app = -1;
560         if (mVocab != null) {
561             app = mVocab.getOrDefault(mPackageName, -1);
562         }
563 
564         // Denotes whether we should proceed with the gesture. Even if it is false, we may want to
565         // log it assuming it is not invalid due to exclusion.
566         boolean withinRange = x < mEdgeWidthLeft + mLeftInset
567                 || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset);
568         if (withinRange) {
569             int results = -1;
570 
571             // Check if we are within the tightest bounds beyond which we would not need to run the
572             // ML model
573             boolean withinMinRange = x < mMLEnableWidth + mLeftInset
574                     || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset);
575             if (!withinMinRange && mUseMLModel
576                     && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) {
577                 withinRange = (results == 1);
578             }
579         }
580 
581         // For debugging purposes
582         if (mPredictionLog.size() >= MAX_LOGGED_PREDICTIONS) {
583             mPredictionLog.removeFirst();
584         }
585         mPredictionLog.addLast(String.format("[%d,%d,%d,%f,%d]",
586                 x, y, app, mMLResults, withinRange ? 1 : 0));
587 
588         // Always allow if the user is in a transient sticky immersive state
589         if (mIsNavBarShownTransiently) {
590             mLogGesture = true;
591             return withinRange;
592         }
593 
594         if (mExcludeRegion.contains(x, y)) {
595             if (withinRange) {
596                 // Log as exclusion only if it is in acceptable range in the first place.
597                 mOverviewProxyService.notifyBackAction(
598                         false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
599                 // We don't have the end point for logging purposes.
600                 mEndPoint.x = -1;
601                 mEndPoint.y = -1;
602                 mLogGesture = true;
603                 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED);
604             }
605             return false;
606         }
607 
608         mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y);
609         mLogGesture = true;
610         return withinRange;
611     }
612 
cancelGesture(MotionEvent ev)613     private void cancelGesture(MotionEvent ev) {
614         // Send action cancel to reset all the touch events
615         mAllowGesture = false;
616         mLogGesture = false;
617         mInRejectedExclusion = false;
618         MotionEvent cancelEv = MotionEvent.obtain(ev);
619         cancelEv.setAction(MotionEvent.ACTION_CANCEL);
620         mEdgeBackPlugin.onMotionEvent(cancelEv);
621         cancelEv.recycle();
622     }
623 
logGesture(int backType)624     private void logGesture(int backType) {
625         if (!mLogGesture) {
626             return;
627         }
628         mLogGesture = false;
629         String logPackageName = "";
630         // Due to privacy, only top 100 most used apps by all users can be logged.
631         if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) {
632             logPackageName = mPackageName;
633         }
634         SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType,
635                 (int) mDownPoint.y, mIsOnLeftEdge
636                         ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT
637                         : SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT,
638                 (int) mDownPoint.x, (int) mDownPoint.y,
639                 (int) mEndPoint.x, (int) mEndPoint.y,
640                 mEdgeWidthLeft + mLeftInset,
641                 mDisplaySize.x - (mEdgeWidthRight + mRightInset),
642                 mUseMLModel ? mMLResults : -2, logPackageName);
643     }
644 
onMotionEvent(MotionEvent ev)645     private void onMotionEvent(MotionEvent ev) {
646         int action = ev.getActionMasked();
647         if (action == MotionEvent.ACTION_DOWN) {
648             // Verify if this is in within the touch region and we aren't in immersive mode, and
649             // either the bouncer is showing or the notification panel is hidden
650             mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
651             mMLResults = 0;
652             mLogGesture = false;
653             mInRejectedExclusion = false;
654             mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
655                     && !mGestureBlockingActivityRunning
656                     && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
657                     && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
658             if (mAllowGesture) {
659                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
660                 mEdgeBackPlugin.onMotionEvent(ev);
661             }
662             if (mLogGesture) {
663                 mDownPoint.set(ev.getX(), ev.getY());
664                 mEndPoint.set(-1, -1);
665                 mThresholdCrossed = false;
666             }
667         } else if (mAllowGesture || mLogGesture) {
668             if (!mThresholdCrossed) {
669                 mEndPoint.x = (int) ev.getX();
670                 mEndPoint.y = (int) ev.getY();
671                 if (action == MotionEvent.ACTION_POINTER_DOWN) {
672                     if (mAllowGesture) {
673                         logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
674                         // We do not support multi touch for back gesture
675                         cancelGesture(ev);
676                     }
677                     mLogGesture = false;
678                     return;
679                 } else if (action == MotionEvent.ACTION_MOVE) {
680                     if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
681                         if (mAllowGesture) {
682                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS);
683                             cancelGesture(ev);
684                         }
685                         mLogGesture = false;
686                         return;
687                     }
688                     float dx = Math.abs(ev.getX() - mDownPoint.x);
689                     float dy = Math.abs(ev.getY() - mDownPoint.y);
690                     if (dy > dx && dy > mTouchSlop) {
691                         if (mAllowGesture) {
692                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE);
693                             cancelGesture(ev);
694                         }
695                         mLogGesture = false;
696                         return;
697                     } else if (dx > dy && dx > mTouchSlop) {
698                         if (mAllowGesture) {
699                             mThresholdCrossed = true;
700                             // Capture inputs
701                             mInputMonitor.pilferPointers();
702                         } else {
703                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE);
704                         }
705                     }
706                 }
707             }
708 
709             if (mAllowGesture) {
710                 // forward touch
711                 mEdgeBackPlugin.onMotionEvent(ev);
712             }
713         }
714 
715         Dependency.get(ProtoTracer.class).update();
716     }
717 
updateDisabledForQuickstep()718     private void updateDisabledForQuickstep() {
719         int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation();
720         mDisabledForQuickstep = mStartingQuickstepRotation > -1 &&
721                 mStartingQuickstepRotation != rotation;
722     }
723 
724     @Override
onDisplayAdded(int displayId)725     public void onDisplayAdded(int displayId) { }
726 
727     @Override
onDisplayRemoved(int displayId)728     public void onDisplayRemoved(int displayId) { }
729 
730     @Override
onDisplayChanged(int displayId)731     public void onDisplayChanged(int displayId) {
732         if (mStartingQuickstepRotation > -1) {
733             updateDisabledForQuickstep();
734         }
735 
736         if (displayId == mDisplayId) {
737             updateDisplaySize();
738         }
739     }
740 
updateDisplaySize()741     private void updateDisplaySize() {
742         mContext.getDisplay().getRealSize(mDisplaySize);
743         if (mEdgeBackPlugin != null) {
744             mEdgeBackPlugin.setDisplaySize(mDisplaySize);
745         }
746     }
747 
sendEvent(int action, int code)748     private void sendEvent(int action, int code) {
749         long when = SystemClock.uptimeMillis();
750         final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
751                 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
752                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
753                 InputDevice.SOURCE_KEYBOARD);
754 
755         // Bubble controller will give us a valid display id if it should get the back event
756         BubbleController bubbleController = Dependency.get(BubbleController.class);
757         int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
758         if (code == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
759             ev.setDisplayId(bubbleDisplayId);
760         }
761         InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
762     }
763 
setInsets(int leftInset, int rightInset)764     public void setInsets(int leftInset, int rightInset) {
765         mLeftInset = leftInset;
766         mRightInset = rightInset;
767         if (mEdgeBackPlugin != null) {
768             mEdgeBackPlugin.setInsets(leftInset, rightInset);
769         }
770     }
771 
dump(PrintWriter pw)772     public void dump(PrintWriter pw) {
773         pw.println("EdgeBackGestureHandler:");
774         pw.println("  mIsEnabled=" + mIsEnabled);
775         pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
776         pw.println("  mAllowGesture=" + mAllowGesture);
777         pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
778         pw.println("  mStartingQuickstepRotation=" + mStartingQuickstepRotation);
779         pw.println("  mInRejectedExclusion" + mInRejectedExclusion);
780         pw.println("  mExcludeRegion=" + mExcludeRegion);
781         pw.println("  mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
782         pw.println("  mIsAttached=" + mIsAttached);
783         pw.println("  mEdgeWidthLeft=" + mEdgeWidthLeft);
784         pw.println("  mEdgeWidthRight=" + mEdgeWidthRight);
785         pw.println("  mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
786         pw.println("  mPredictionLog=" + String.join(";", mPredictionLog));
787     }
788 
isGestureBlockingActivityRunning()789     private boolean isGestureBlockingActivityRunning() {
790         ActivityManager.RunningTaskInfo runningTask =
791                 ActivityManagerWrapper.getInstance().getRunningTask();
792         ComponentName topActivity = runningTask == null ? null : runningTask.topActivity;
793         if (topActivity != null) {
794             mPackageName = topActivity.getPackageName();
795         } else {
796             mPackageName = "_UNKNOWN";
797         }
798         return topActivity != null && mGestureBlockingActivities.contains(topActivity);
799     }
800 
801     @Override
writeToProto(SystemUiTraceProto proto)802     public void writeToProto(SystemUiTraceProto proto) {
803         if (proto.edgeBackGestureHandler == null) {
804             proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto();
805         }
806         proto.edgeBackGestureHandler.allowGesture = mAllowGesture;
807     }
808 
809     class SysUiInputEventReceiver extends InputEventReceiver {
SysUiInputEventReceiver(InputChannel channel, Looper looper)810         SysUiInputEventReceiver(InputChannel channel, Looper looper) {
811             super(channel, looper);
812         }
813 
onInputEvent(InputEvent event)814         public void onInputEvent(InputEvent event) {
815             EdgeBackGestureHandler.this.onInputEvent(event);
816             finishInputEvent(event, true);
817         }
818     }
819 }
820