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.broadcast.BroadcastDispatcher;
60 import com.android.systemui.bubbles.BubbleController;
61 import com.android.systemui.model.SysUiState;
62 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
63 import com.android.systemui.plugins.PluginListener;
64 import com.android.systemui.recents.OverviewProxyService;
65 import com.android.systemui.settings.CurrentUserTracker;
66 import com.android.systemui.shared.plugins.PluginManager;
67 import com.android.systemui.shared.system.ActivityManagerWrapper;
68 import com.android.systemui.shared.system.QuickStepContract;
69 import com.android.systemui.shared.system.SysUiStatsLog;
70 import com.android.systemui.shared.system.TaskStackChangeListener;
71 import com.android.systemui.shared.tracing.ProtoTraceable;
72 import com.android.systemui.tracing.ProtoTracer;
73 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
74 import com.android.systemui.tracing.nano.SystemUiTraceProto;
75 
76 import java.io.PrintWriter;
77 import java.util.ArrayList;
78 import java.util.List;
79 import java.util.concurrent.Executor;
80 
81 /**
82  * Utility class to handle edge swipes for back gesture
83  */
84 public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener,
85         PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
86 
87     private static final String TAG = "EdgeBackGestureHandler";
88     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
89             "gestures.back_timeout", 250);
90 
91     private ISystemGestureExclusionListener mGestureExclusionListener =
92             new ISystemGestureExclusionListener.Stub() {
93                 @Override
94                 public void onSystemGestureExclusionChanged(int displayId,
95                         Region systemGestureExclusion, Region unrestrictedOrNull) {
96                     if (displayId == mDisplayId) {
97                         mMainExecutor.execute(() -> {
98                             mExcludeRegion.set(systemGestureExclusion);
99                             mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null
100                                     ? unrestrictedOrNull : systemGestureExclusion);
101                         });
102                     }
103                 }
104             };
105 
106     private OverviewProxyService.OverviewProxyListener mQuickSwitchListener =
107             new OverviewProxyService.OverviewProxyListener() {
108                 @Override
109                 public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
110                     mStartingQuickstepRotation = rotation;
111                     updateDisabledForQuickstep();
112                 }
113             };
114 
115     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
116         @Override
117         public void onTaskStackChanged() {
118             mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
119         }
120     };
121 
122     private final Context mContext;
123     private final OverviewProxyService mOverviewProxyService;
124     private final Runnable mStateChangeCallback;
125 
126     private final PluginManager mPluginManager;
127     // Activities which should not trigger Back gesture.
128     private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
129 
130     private final Point mDisplaySize = new Point();
131     private final int mDisplayId;
132 
133     private final Executor mMainExecutor;
134 
135     private final Region mExcludeRegion = new Region();
136     private final Region mUnrestrictedExcludeRegion = new Region();
137 
138     // The left side edge width where touch down is allowed
139     private int mEdgeWidthLeft;
140     // The right side edge width where touch down is allowed
141     private int mEdgeWidthRight;
142     // The bottom gesture area height
143     private float mBottomGestureHeight;
144     // The slop to distinguish between horizontal and vertical motion
145     private float mTouchSlop;
146     // Duration after which we consider the event as longpress.
147     private final int mLongPressTimeout;
148     private int mStartingQuickstepRotation = -1;
149     // We temporarily disable back gesture when user is quickswitching
150     // between apps of different orientations
151     private boolean mDisabledForQuickstep;
152 
153     private final PointF mDownPoint = new PointF();
154     private final PointF mEndPoint = new PointF();
155     private boolean mThresholdCrossed = false;
156     private boolean mAllowGesture = false;
157     private boolean mLogGesture = false;
158     private boolean mInRejectedExclusion = false;
159     private boolean mIsOnLeftEdge;
160 
161     private boolean mIsAttached;
162     private boolean mIsGesturalModeEnabled;
163     private boolean mIsEnabled;
164     private boolean mIsNavBarShownTransiently;
165     private boolean mIsBackGestureAllowed;
166     private boolean mGestureBlockingActivityRunning;
167 
168     private InputMonitor mInputMonitor;
169     private InputEventReceiver mInputEventReceiver;
170 
171     private NavigationEdgeBackPlugin mEdgeBackPlugin;
172     private int mLeftInset;
173     private int mRightInset;
174     private int mSysUiFlags;
175 
176     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
177 
178     private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
179             new NavigationEdgeBackPlugin.BackCallback() {
180                 @Override
181                 public void triggerBack() {
182                     sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
183                     sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
184 
185                     mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
186                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
187                     logGesture(mInRejectedExclusion
188                             ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
189                             : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
190                 }
191 
192                 @Override
193                 public void cancelBack() {
194                     logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
195                     mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
196                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
197                 }
198             };
199 
EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiFlagContainer, PluginManager pluginManager, Runnable stateChangeCallback)200     public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
201             SysUiState sysUiFlagContainer, PluginManager pluginManager,
202             Runnable stateChangeCallback) {
203         super(Dependency.get(BroadcastDispatcher.class));
204         mContext = context;
205         mDisplayId = context.getDisplayId();
206         mMainExecutor = context.getMainExecutor();
207         mOverviewProxyService = overviewProxyService;
208         mPluginManager = pluginManager;
209         mStateChangeCallback = stateChangeCallback;
210         ComponentName recentsComponentName = ComponentName.unflattenFromString(
211                 context.getString(com.android.internal.R.string.config_recentsComponentName));
212         if (recentsComponentName != null) {
213             String recentsPackageName = recentsComponentName.getPackageName();
214             PackageManager manager = context.getPackageManager();
215             try {
216                 Resources resources = manager.getResourcesForApplication(recentsPackageName);
217                 int resId = resources.getIdentifier(
218                         "gesture_blocking_activities", "array", recentsPackageName);
219 
220                 if (resId == 0) {
221                     Log.e(TAG, "No resource found for gesture-blocking activities");
222                 } else {
223                     String[] gestureBlockingActivities = resources.getStringArray(resId);
224                     for (String gestureBlockingActivity : gestureBlockingActivities) {
225                         mGestureBlockingActivities.add(
226                                 ComponentName.unflattenFromString(gestureBlockingActivity));
227                     }
228                 }
229             } catch (NameNotFoundException e) {
230                 Log.e(TAG, "Failed to add gesture blocking activities", e);
231             }
232         }
233 
234         Dependency.get(ProtoTracer.class).add(this);
235         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
236                 ViewConfiguration.getLongPressTimeout());
237 
238         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
239                 mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
240 
241         updateCurrentUserResources();
242         sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
243     }
244 
updateCurrentUserResources()245     public void updateCurrentUserResources() {
246         Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext()
247                 .getResources();
248         mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
249         mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
250         mIsBackGestureAllowed =
251                 !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
252 
253         final DisplayMetrics dm = res.getDisplayMetrics();
254         final float defaultGestureHeight = res.getDimension(
255                 com.android.internal.R.dimen.navigation_bar_gesture_height) / dm.density;
256         final float gestureHeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
257                 SystemUiDeviceConfigFlags.BACK_GESTURE_BOTTOM_HEIGHT,
258                 defaultGestureHeight);
259         mBottomGestureHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, gestureHeight,
260                 dm);
261 
262         // Reduce the default touch slop to ensure that we can intercept the gesture
263         // before the app starts to react to it.
264         // TODO(b/130352502) Tune this value and extract into a constant
265         final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
266                         SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f);
267         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop() * backGestureSlop;
268     }
269 
onNavigationSettingsChanged()270     private void onNavigationSettingsChanged() {
271         boolean wasBackAllowed = isHandlingGestures();
272         updateCurrentUserResources();
273         if (wasBackAllowed != isHandlingGestures()) {
274             mStateChangeCallback.run();
275         }
276     }
277 
278     @Override
onUserSwitched(int newUserId)279     public void onUserSwitched(int newUserId) {
280         updateIsEnabled();
281         updateCurrentUserResources();
282     }
283 
284     /**
285      * @see NavigationBarView#onAttachedToWindow()
286      */
onNavBarAttached()287     public void onNavBarAttached() {
288         mIsAttached = true;
289         mOverviewProxyService.addCallback(mQuickSwitchListener);
290         updateIsEnabled();
291         startTracking();
292     }
293 
294     /**
295      * @see NavigationBarView#onDetachedFromWindow()
296      */
onNavBarDetached()297     public void onNavBarDetached() {
298         mIsAttached = false;
299         mOverviewProxyService.removeCallback(mQuickSwitchListener);
300         updateIsEnabled();
301         stopTracking();
302     }
303 
304     /**
305      * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
306      */
onNavigationModeChanged(int mode)307     public void onNavigationModeChanged(int mode) {
308         mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
309         updateIsEnabled();
310         updateCurrentUserResources();
311     }
312 
onNavBarTransientStateChanged(boolean isTransient)313     public void onNavBarTransientStateChanged(boolean isTransient) {
314         mIsNavBarShownTransiently = isTransient;
315     }
316 
disposeInputChannel()317     private void disposeInputChannel() {
318         if (mInputEventReceiver != null) {
319             mInputEventReceiver.dispose();
320             mInputEventReceiver = null;
321         }
322         if (mInputMonitor != null) {
323             mInputMonitor.dispose();
324             mInputMonitor = null;
325         }
326     }
327 
updateIsEnabled()328     private void updateIsEnabled() {
329         boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
330         if (isEnabled == mIsEnabled) {
331             return;
332         }
333         mIsEnabled = isEnabled;
334         disposeInputChannel();
335 
336         if (mEdgeBackPlugin != null) {
337             mEdgeBackPlugin.onDestroy();
338             mEdgeBackPlugin = null;
339         }
340 
341         if (!mIsEnabled) {
342             mGestureNavigationSettingsObserver.unregister();
343             mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
344             mPluginManager.removePluginListener(this);
345             ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
346 
347             try {
348                 WindowManagerGlobal.getWindowManagerService()
349                         .unregisterSystemGestureExclusionListener(
350                                 mGestureExclusionListener, mDisplayId);
351             } catch (RemoteException | IllegalArgumentException e) {
352                 Log.e(TAG, "Failed to unregister window manager callbacks", e);
353             }
354 
355         } else {
356             mGestureNavigationSettingsObserver.register();
357             updateDisplaySize();
358             mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
359                     mContext.getMainThreadHandler());
360             ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
361 
362             try {
363                 WindowManagerGlobal.getWindowManagerService()
364                         .registerSystemGestureExclusionListener(
365                                 mGestureExclusionListener, mDisplayId);
366             } catch (RemoteException | IllegalArgumentException e) {
367                 Log.e(TAG, "Failed to register window manager callbacks", e);
368             }
369 
370             // Register input event receiver
371             mInputMonitor = InputManager.getInstance().monitorGestureInput(
372                     "edge-swipe", mDisplayId);
373             mInputEventReceiver = new SysUiInputEventReceiver(
374                     mInputMonitor.getInputChannel(), Looper.getMainLooper());
375 
376             // Add a nav bar panel window
377             setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
378             mPluginManager.addPluginListener(
379                     this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
380         }
381     }
382 
383     @Override
onPluginConnected(NavigationEdgeBackPlugin plugin, Context context)384     public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) {
385         setEdgeBackPlugin(plugin);
386     }
387 
388     @Override
onPluginDisconnected(NavigationEdgeBackPlugin plugin)389     public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) {
390         setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
391     }
392 
setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin)393     private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
394         if (mEdgeBackPlugin != null) {
395             mEdgeBackPlugin.onDestroy();
396         }
397         mEdgeBackPlugin = edgeBackPlugin;
398         mEdgeBackPlugin.setBackCallback(mBackCallback);
399         mEdgeBackPlugin.setLayoutParams(createLayoutParams());
400         updateDisplaySize();
401     }
402 
isHandlingGestures()403     public boolean isHandlingGestures() {
404         return mIsEnabled && mIsBackGestureAllowed;
405     }
406 
createLayoutParams()407     private WindowManager.LayoutParams createLayoutParams() {
408         Resources resources = mContext.getResources();
409         WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
410                 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
411                 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
412                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
413                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
414                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
415                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
416                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
417                 PixelFormat.TRANSLUCENT);
418         layoutParams.privateFlags |=
419                 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
420         layoutParams.setTitle(TAG + mContext.getDisplayId());
421         layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
422         layoutParams.windowAnimations = 0;
423         layoutParams.setFitInsetsTypes(0 /* types */);
424         return layoutParams;
425     }
426 
onInputEvent(InputEvent ev)427     private void onInputEvent(InputEvent ev) {
428         if (ev instanceof MotionEvent) {
429             onMotionEvent((MotionEvent) ev);
430         }
431     }
432 
isWithinTouchRegion(int x, int y)433     private boolean isWithinTouchRegion(int x, int y) {
434         // Disallow if we are in the bottom gesture area
435         if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
436             return false;
437         }
438 
439         // If the point is way too far (twice the margin), it is
440         // not interesting to us for logging purposes, nor we
441         // should process it.  Simply return false and keep
442         // mLogGesture = false.
443         if (x > 2 * (mEdgeWidthLeft + mLeftInset)
444                 && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) {
445             return false;
446         }
447 
448         // Denotes whether we should proceed with the gesture.
449         // Even if it is false, we may want to log it assuming
450         // it is not invalid due to exclusion.
451         boolean withinRange = x <= mEdgeWidthLeft + mLeftInset
452                 || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset);
453 
454         // Always allow if the user is in a transient sticky immersive state
455         if (mIsNavBarShownTransiently) {
456             mLogGesture = true;
457             return withinRange;
458         }
459 
460         if (mExcludeRegion.contains(x, y)) {
461             if (withinRange) {
462                 // Log as exclusion only if it is in acceptable range in the first place.
463                 mOverviewProxyService.notifyBackAction(
464                         false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
465                 // We don't have the end point for logging purposes.
466                 mEndPoint.x = -1;
467                 mEndPoint.y = -1;
468                 mLogGesture = true;
469                 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED);
470             }
471             return false;
472         }
473 
474         mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y);
475         mLogGesture = true;
476         return withinRange;
477     }
478 
cancelGesture(MotionEvent ev)479     private void cancelGesture(MotionEvent ev) {
480         // Send action cancel to reset all the touch events
481         mAllowGesture = false;
482         mLogGesture = false;
483         mInRejectedExclusion = false;
484         MotionEvent cancelEv = MotionEvent.obtain(ev);
485         cancelEv.setAction(MotionEvent.ACTION_CANCEL);
486         mEdgeBackPlugin.onMotionEvent(cancelEv);
487         cancelEv.recycle();
488     }
489 
logGesture(int backType)490     private void logGesture(int backType) {
491         if (!mLogGesture) {
492             return;
493         }
494         mLogGesture = false;
495         SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType,
496                 (int) mDownPoint.y, mIsOnLeftEdge
497                         ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT
498                         : SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT,
499                 (int) mDownPoint.x, (int) mDownPoint.y,
500                 (int) mEndPoint.x, (int) mEndPoint.y,
501                 mEdgeWidthLeft + mLeftInset,
502                 mDisplaySize.x - (mEdgeWidthRight + mRightInset));
503     }
504 
onMotionEvent(MotionEvent ev)505     private void onMotionEvent(MotionEvent ev) {
506         int action = ev.getActionMasked();
507         if (action == MotionEvent.ACTION_DOWN) {
508             // Verify if this is in within the touch region and we aren't in immersive mode, and
509             // either the bouncer is showing or the notification panel is hidden
510             mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
511             mLogGesture = false;
512             mInRejectedExclusion = false;
513             mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
514                     && !mGestureBlockingActivityRunning
515                     && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
516                     && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
517             if (mAllowGesture) {
518                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
519                 mEdgeBackPlugin.onMotionEvent(ev);
520             }
521             if (mLogGesture) {
522                 mDownPoint.set(ev.getX(), ev.getY());
523                 mEndPoint.set(-1, -1);
524                 mThresholdCrossed = false;
525             }
526         } else if (mAllowGesture || mLogGesture) {
527             if (!mThresholdCrossed) {
528                 mEndPoint.x = (int) ev.getX();
529                 mEndPoint.y = (int) ev.getY();
530                 if (action == MotionEvent.ACTION_POINTER_DOWN) {
531                     if (mAllowGesture) {
532                         logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
533                         // We do not support multi touch for back gesture
534                         cancelGesture(ev);
535                     }
536                     mLogGesture = false;
537                     return;
538                 } else if (action == MotionEvent.ACTION_MOVE) {
539                     if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
540                         if (mAllowGesture) {
541                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS);
542                             cancelGesture(ev);
543                         }
544                         mLogGesture = false;
545                         return;
546                     }
547                     float dx = Math.abs(ev.getX() - mDownPoint.x);
548                     float dy = Math.abs(ev.getY() - mDownPoint.y);
549                     if (dy > dx && dy > mTouchSlop) {
550                         if (mAllowGesture) {
551                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE);
552                             cancelGesture(ev);
553                         }
554                         mLogGesture = false;
555                         return;
556                     } else if (dx > dy && dx > mTouchSlop) {
557                         if (mAllowGesture) {
558                             mThresholdCrossed = true;
559                             // Capture inputs
560                             mInputMonitor.pilferPointers();
561                         } else {
562                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE);
563                         }
564                     }
565                 }
566             }
567 
568             if (mAllowGesture) {
569                 // forward touch
570                 mEdgeBackPlugin.onMotionEvent(ev);
571             }
572         }
573 
574         Dependency.get(ProtoTracer.class).update();
575     }
576 
updateDisabledForQuickstep()577     private void updateDisabledForQuickstep() {
578         int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation();
579         mDisabledForQuickstep = mStartingQuickstepRotation > -1 &&
580                 mStartingQuickstepRotation != rotation;
581     }
582 
583     @Override
onDisplayAdded(int displayId)584     public void onDisplayAdded(int displayId) { }
585 
586     @Override
onDisplayRemoved(int displayId)587     public void onDisplayRemoved(int displayId) { }
588 
589     @Override
onDisplayChanged(int displayId)590     public void onDisplayChanged(int displayId) {
591         if (mStartingQuickstepRotation > -1) {
592             updateDisabledForQuickstep();
593         }
594 
595         if (displayId == mDisplayId) {
596             updateDisplaySize();
597         }
598     }
599 
updateDisplaySize()600     private void updateDisplaySize() {
601         mContext.getDisplay().getRealSize(mDisplaySize);
602         if (mEdgeBackPlugin != null) {
603             mEdgeBackPlugin.setDisplaySize(mDisplaySize);
604         }
605     }
606 
sendEvent(int action, int code)607     private void sendEvent(int action, int code) {
608         long when = SystemClock.uptimeMillis();
609         final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
610                 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
611                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
612                 InputDevice.SOURCE_KEYBOARD);
613 
614         // Bubble controller will give us a valid display id if it should get the back event
615         BubbleController bubbleController = Dependency.get(BubbleController.class);
616         int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
617         if (code == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
618             ev.setDisplayId(bubbleDisplayId);
619         }
620         InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
621     }
622 
setInsets(int leftInset, int rightInset)623     public void setInsets(int leftInset, int rightInset) {
624         mLeftInset = leftInset;
625         mRightInset = rightInset;
626         if (mEdgeBackPlugin != null) {
627             mEdgeBackPlugin.setInsets(leftInset, rightInset);
628         }
629     }
630 
dump(PrintWriter pw)631     public void dump(PrintWriter pw) {
632         pw.println("EdgeBackGestureHandler:");
633         pw.println("  mIsEnabled=" + mIsEnabled);
634         pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
635         pw.println("  mAllowGesture=" + mAllowGesture);
636         pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
637         pw.println("  mStartingQuickstepRotation=" + mStartingQuickstepRotation);
638         pw.println("  mInRejectedExclusion" + mInRejectedExclusion);
639         pw.println("  mExcludeRegion=" + mExcludeRegion);
640         pw.println("  mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
641         pw.println("  mIsAttached=" + mIsAttached);
642         pw.println("  mEdgeWidthLeft=" + mEdgeWidthLeft);
643         pw.println("  mEdgeWidthRight=" + mEdgeWidthRight);
644     }
645 
isGestureBlockingActivityRunning()646     private boolean isGestureBlockingActivityRunning() {
647         ActivityManager.RunningTaskInfo runningTask =
648                 ActivityManagerWrapper.getInstance().getRunningTask();
649         ComponentName topActivity = runningTask == null ? null : runningTask.topActivity;
650         return topActivity != null && mGestureBlockingActivities.contains(topActivity);
651     }
652 
653     @Override
writeToProto(SystemUiTraceProto proto)654     public void writeToProto(SystemUiTraceProto proto) {
655         if (proto.edgeBackGestureHandler == null) {
656             proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto();
657         }
658         proto.edgeBackGestureHandler.allowGesture = mAllowGesture;
659     }
660 
661     class SysUiInputEventReceiver extends InputEventReceiver {
SysUiInputEventReceiver(InputChannel channel, Looper looper)662         SysUiInputEventReceiver(InputChannel channel, Looper looper) {
663             super(channel, looper);
664         }
665 
onInputEvent(InputEvent event)666         public void onInputEvent(InputEvent event) {
667             EdgeBackGestureHandler.this.onInputEvent(event);
668             finishInputEvent(event, true);
669         }
670     }
671 }
672