1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.systemui.navigationbar.gestural;
17 
18 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
19 
20 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
21 
22 import android.app.ActivityManager;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 import android.content.pm.PackageManager.NameNotFoundException;
27 import android.content.res.Configuration;
28 import android.content.res.Resources;
29 import android.graphics.PixelFormat;
30 import android.graphics.Point;
31 import android.graphics.PointF;
32 import android.graphics.Rect;
33 import android.graphics.Region;
34 import android.hardware.input.InputManager;
35 import android.os.Looper;
36 import android.os.RemoteException;
37 import android.os.SystemClock;
38 import android.os.SystemProperties;
39 import android.provider.DeviceConfig;
40 import android.util.DisplayMetrics;
41 import android.util.Log;
42 import android.util.TypedValue;
43 import android.view.Choreographer;
44 import android.view.Display;
45 import android.view.ISystemGestureExclusionListener;
46 import android.view.IWindowManager;
47 import android.view.InputDevice;
48 import android.view.InputEvent;
49 import android.view.InputMonitor;
50 import android.view.KeyCharacterMap;
51 import android.view.KeyEvent;
52 import android.view.MotionEvent;
53 import android.view.Surface;
54 import android.view.ViewConfiguration;
55 import android.view.WindowManager;
56 import android.view.WindowMetrics;
57 
58 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
59 import com.android.internal.policy.GestureNavigationSettingsObserver;
60 import com.android.systemui.R;
61 import com.android.systemui.SystemUIFactory;
62 import com.android.systemui.broadcast.BroadcastDispatcher;
63 import com.android.systemui.dagger.qualifiers.Main;
64 import com.android.systemui.model.SysUiState;
65 import com.android.systemui.navigationbar.NavigationBarView;
66 import com.android.systemui.navigationbar.NavigationModeController;
67 import com.android.systemui.plugins.FalsingManager;
68 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
69 import com.android.systemui.plugins.PluginListener;
70 import com.android.systemui.recents.OverviewProxyService;
71 import com.android.systemui.settings.CurrentUserTracker;
72 import com.android.systemui.shared.plugins.PluginManager;
73 import com.android.systemui.shared.system.ActivityManagerWrapper;
74 import com.android.systemui.shared.system.InputChannelCompat;
75 import com.android.systemui.shared.system.QuickStepContract;
76 import com.android.systemui.shared.system.SysUiStatsLog;
77 import com.android.systemui.shared.system.TaskStackChangeListener;
78 import com.android.systemui.shared.system.TaskStackChangeListeners;
79 import com.android.systemui.shared.tracing.ProtoTraceable;
80 import com.android.systemui.tracing.ProtoTracer;
81 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
82 import com.android.systemui.tracing.nano.SystemUiTraceProto;
83 
84 import java.io.PrintWriter;
85 import java.util.ArrayDeque;
86 import java.util.ArrayList;
87 import java.util.List;
88 import java.util.Map;
89 import java.util.concurrent.Executor;
90 
91 import javax.inject.Inject;
92 
93 /**
94  * Utility class to handle edge swipes for back gesture
95  */
96 public class EdgeBackGestureHandler extends CurrentUserTracker
97         implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
98 
99     private static final String TAG = "EdgeBackGestureHandler";
100     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
101             "gestures.back_timeout", 250);
102 
103     private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
104     private static final int MAX_NUM_LOGGED_GESTURES = 10;
105 
106     static final boolean DEBUG_MISSING_GESTURE = false;
107     static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
108 
109     private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
110             SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
111 
112     private ISystemGestureExclusionListener mGestureExclusionListener =
113             new ISystemGestureExclusionListener.Stub() {
114                 @Override
115                 public void onSystemGestureExclusionChanged(int displayId,
116                         Region systemGestureExclusion, Region unrestrictedOrNull) {
117                     if (displayId == mDisplayId) {
118                         mMainExecutor.execute(() -> {
119                             mExcludeRegion.set(systemGestureExclusion);
120                             mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null
121                                     ? unrestrictedOrNull : systemGestureExclusion);
122                         });
123                     }
124                 }
125             };
126 
127     private OverviewProxyService.OverviewProxyListener mQuickSwitchListener =
128             new OverviewProxyService.OverviewProxyListener() {
129                 @Override
130                 public void onPrioritizedRotation(@Surface.Rotation int rotation) {
131                     mStartingQuickstepRotation = rotation;
132                     updateDisabledForQuickstep(mContext.getResources().getConfiguration());
133                 }
134             };
135 
136     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
137         @Override
138         public void onTaskStackChanged() {
139             mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
140         }
141         @Override
142         public void onTaskCreated(int taskId, ComponentName componentName) {
143             if (componentName != null) {
144                 mPackageName = componentName.getPackageName();
145             } else {
146                 mPackageName = "_UNKNOWN";
147             }
148         }
149 
150         @Override
151         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
152             mIsInPipMode = true;
153         }
154 
155         @Override
156         public void onActivityUnpinned() {
157             mIsInPipMode = false;
158         }
159     };
160 
161     private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
162             new DeviceConfig.OnPropertiesChangedListener() {
163                 @Override
164                 public void onPropertiesChanged(DeviceConfig.Properties properties) {
165                     if (DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())
166                             && (properties.getKeyset().contains(
167                                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD)
168                             || properties.getKeyset().contains(
169                                     SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL)
170                             || properties.getKeyset().contains(
171                                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME))) {
172                         updateMLModelState();
173                     }
174                 }
175             };
176 
177 
178     private final Context mContext;
179     private final OverviewProxyService mOverviewProxyService;
180     private final SysUiState mSysUiState;
181     private Runnable mStateChangeCallback;
182 
183     private final PluginManager mPluginManager;
184     private final ProtoTracer mProtoTracer;
185     private final NavigationModeController mNavigationModeController;
186     private final ViewConfiguration mViewConfiguration;
187     private final WindowManager mWindowManager;
188     private final IWindowManager mWindowManagerService;
189     private final FalsingManager mFalsingManager;
190     // Activities which should not trigger Back gesture.
191     private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
192 
193     private final Point mDisplaySize = new Point();
194     private final int mDisplayId;
195 
196     private final Executor mMainExecutor;
197 
198     private final Rect mPipExcludedBounds = new Rect();
199     private final Rect mNavBarOverlayExcludedBounds = new Rect();
200     private final Region mExcludeRegion = new Region();
201     private final Region mUnrestrictedExcludeRegion = new Region();
202 
203     // The left side edge width where touch down is allowed
204     private int mEdgeWidthLeft;
205     // The right side edge width where touch down is allowed
206     private int mEdgeWidthRight;
207     // The bottom gesture area height
208     private float mBottomGestureHeight;
209     // The slop to distinguish between horizontal and vertical motion
210     private float mTouchSlop;
211     // Duration after which we consider the event as longpress.
212     private final int mLongPressTimeout;
213     private int mStartingQuickstepRotation = -1;
214     // We temporarily disable back gesture when user is quickswitching
215     // between apps of different orientations
216     private boolean mDisabledForQuickstep;
217 
218     private final PointF mDownPoint = new PointF();
219     private final PointF mEndPoint = new PointF();
220     private boolean mThresholdCrossed = false;
221     private boolean mAllowGesture = false;
222     private boolean mLogGesture = false;
223     private boolean mInRejectedExclusion = false;
224     private boolean mIsOnLeftEdge;
225 
226     private boolean mIsAttached;
227     private boolean mIsGesturalModeEnabled;
228     private boolean mIsEnabled;
229     private boolean mIsNavBarShownTransiently;
230     private boolean mIsBackGestureAllowed;
231     private boolean mGestureBlockingActivityRunning;
232     private boolean mIsInPipMode;
233 
234     private InputMonitor mInputMonitor;
235     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
236 
237     private NavigationEdgeBackPlugin mEdgeBackPlugin;
238     private int mLeftInset;
239     private int mRightInset;
240     private int mSysUiFlags;
241 
242     // For Tf-Lite model.
243     private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
244     private Map<String, Integer> mVocab;
245     private boolean mUseMLModel;
246     // minimum width below which we do not run the model
247     private int mMLEnableWidth;
248     private float mMLModelThreshold;
249     private String mPackageName;
250     private float mMLResults;
251 
252     // For debugging
253     private LogArray mPredictionLog = new LogArray(MAX_NUM_LOGGED_PREDICTIONS);
254     private LogArray mGestureLogInsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
255     private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
256 
257     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
258 
259     private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
260             new NavigationEdgeBackPlugin.BackCallback() {
261                 @Override
262                 public void triggerBack() {
263                     // Notify FalsingManager that an intentional gesture has occurred.
264                     // TODO(b/186519446): use a different method than isFalseTouch
265                     mFalsingManager.isFalseTouch(BACK_GESTURE);
266                     boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
267                     boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
268                     if (DEBUG_MISSING_GESTURE) {
269                         Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown
270                                 + ", up=" + sendUp);
271                     }
272 
273                     mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
274                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
275                     logGesture(mInRejectedExclusion
276                             ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
277                             : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
278                 }
279 
280                 @Override
281                 public void cancelBack() {
282                     logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
283                     mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
284                             (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
285                 }
286             };
287 
288     private final SysUiState.SysUiStateCallback mSysUiStateCallback =
289             new SysUiState.SysUiStateCallback() {
290         @Override
291         public void onSystemUiStateChanged(int sysUiFlags) {
292             mSysUiFlags = sysUiFlags;
293         }
294     };
295 
296 
EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, NavigationModeController navigationModeController, ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, FalsingManager falsingManager)297     EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
298             SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor,
299             BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer,
300             NavigationModeController navigationModeController, ViewConfiguration viewConfiguration,
301             WindowManager windowManager, IWindowManager windowManagerService,
302             FalsingManager falsingManager) {
303         super(broadcastDispatcher);
304         mContext = context;
305         mDisplayId = context.getDisplayId();
306         mMainExecutor = executor;
307         mOverviewProxyService = overviewProxyService;
308         mSysUiState = sysUiState;
309         mPluginManager = pluginManager;
310         mProtoTracer = protoTracer;
311         mNavigationModeController = navigationModeController;
312         mViewConfiguration = viewConfiguration;
313         mWindowManager = windowManager;
314         mWindowManagerService = windowManagerService;
315         mFalsingManager = falsingManager;
316         ComponentName recentsComponentName = ComponentName.unflattenFromString(
317                 context.getString(com.android.internal.R.string.config_recentsComponentName));
318         if (recentsComponentName != null) {
319             String recentsPackageName = recentsComponentName.getPackageName();
320             PackageManager manager = context.getPackageManager();
321             try {
322                 Resources resources = manager.getResourcesForApplication(recentsPackageName);
323                 int resId = resources.getIdentifier(
324                         "gesture_blocking_activities", "array", recentsPackageName);
325 
326                 if (resId == 0) {
327                     Log.e(TAG, "No resource found for gesture-blocking activities");
328                 } else {
329                     String[] gestureBlockingActivities = resources.getStringArray(resId);
330                     for (String gestureBlockingActivity : gestureBlockingActivities) {
331                         mGestureBlockingActivities.add(
332                                 ComponentName.unflattenFromString(gestureBlockingActivity));
333                     }
334                 }
335             } catch (NameNotFoundException e) {
336                 Log.e(TAG, "Failed to add gesture blocking activities", e);
337             }
338         }
339         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
340                 ViewConfiguration.getLongPressTimeout());
341 
342         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
343                 mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
344 
345         updateCurrentUserResources();
346     }
347 
setStateChangeCallback(Runnable callback)348     public void setStateChangeCallback(Runnable callback) {
349         mStateChangeCallback = callback;
350     }
351 
updateCurrentUserResources()352     public void updateCurrentUserResources() {
353         Resources res = mNavigationModeController.getCurrentUserContext().getResources();
354         mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
355         mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
356         mIsBackGestureAllowed =
357                 !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
358 
359         final DisplayMetrics dm = res.getDisplayMetrics();
360         final float defaultGestureHeight = res.getDimension(
361                 com.android.internal.R.dimen.navigation_bar_gesture_height) / dm.density;
362         final float gestureHeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
363                 SystemUiDeviceConfigFlags.BACK_GESTURE_BOTTOM_HEIGHT,
364                 defaultGestureHeight);
365         mBottomGestureHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, gestureHeight,
366                 dm);
367 
368         // Set the minimum bounds to activate ML to 12dp or the minimum of configured values
369         mMLEnableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12.0f, dm);
370         if (mMLEnableWidth > mEdgeWidthRight) mMLEnableWidth = mEdgeWidthRight;
371         if (mMLEnableWidth > mEdgeWidthLeft) mMLEnableWidth = mEdgeWidthLeft;
372 
373         // Reduce the default touch slop to ensure that we can intercept the gesture
374         // before the app starts to react to it.
375         // TODO(b/130352502) Tune this value and extract into a constant
376         final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
377                         SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f);
378         mTouchSlop = mViewConfiguration.getScaledTouchSlop() * backGestureSlop;
379     }
380 
updateNavigationBarOverlayExcludeRegion(Rect exclude)381     public void updateNavigationBarOverlayExcludeRegion(Rect exclude) {
382         mNavBarOverlayExcludedBounds.set(exclude);
383     }
384 
onNavigationSettingsChanged()385     private void onNavigationSettingsChanged() {
386         boolean wasBackAllowed = isHandlingGestures();
387         updateCurrentUserResources();
388         if (mStateChangeCallback != null && wasBackAllowed != isHandlingGestures()) {
389             mStateChangeCallback.run();
390         }
391     }
392 
393     @Override
onUserSwitched(int newUserId)394     public void onUserSwitched(int newUserId) {
395         updateIsEnabled();
396         updateCurrentUserResources();
397     }
398 
399     /**
400      * @see NavigationBarView#onAttachedToWindow()
401      */
onNavBarAttached()402     public void onNavBarAttached() {
403         mIsAttached = true;
404         mProtoTracer.add(this);
405         mOverviewProxyService.addCallback(mQuickSwitchListener);
406         mSysUiState.addCallback(mSysUiStateCallback);
407         updateIsEnabled();
408         startTracking();
409     }
410 
411     /**
412      * @see NavigationBarView#onDetachedFromWindow()
413      */
onNavBarDetached()414     public void onNavBarDetached() {
415         mIsAttached = false;
416         mProtoTracer.remove(this);
417         mOverviewProxyService.removeCallback(mQuickSwitchListener);
418         mSysUiState.removeCallback(mSysUiStateCallback);
419         updateIsEnabled();
420         stopTracking();
421     }
422 
423     /**
424      * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
425      */
onNavigationModeChanged(int mode)426     public void onNavigationModeChanged(int mode) {
427         mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
428         updateIsEnabled();
429         updateCurrentUserResources();
430     }
431 
onNavBarTransientStateChanged(boolean isTransient)432     public void onNavBarTransientStateChanged(boolean isTransient) {
433         mIsNavBarShownTransiently = isTransient;
434     }
435 
disposeInputChannel()436     private void disposeInputChannel() {
437         if (mInputEventReceiver != null) {
438             mInputEventReceiver.dispose();
439             mInputEventReceiver = null;
440         }
441         if (mInputMonitor != null) {
442             mInputMonitor.dispose();
443             mInputMonitor = null;
444         }
445     }
446 
updateIsEnabled()447     private void updateIsEnabled() {
448         boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
449         if (isEnabled == mIsEnabled) {
450             return;
451         }
452         mIsEnabled = isEnabled;
453         disposeInputChannel();
454 
455         if (mEdgeBackPlugin != null) {
456             mEdgeBackPlugin.onDestroy();
457             mEdgeBackPlugin = null;
458         }
459 
460         if (!mIsEnabled) {
461             mGestureNavigationSettingsObserver.unregister();
462             if (DEBUG_MISSING_GESTURE) {
463                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
464             }
465             mPluginManager.removePluginListener(this);
466             TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
467             DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
468 
469             try {
470                 mWindowManagerService.unregisterSystemGestureExclusionListener(
471                         mGestureExclusionListener, mDisplayId);
472             } catch (RemoteException | IllegalArgumentException e) {
473                 Log.e(TAG, "Failed to unregister window manager callbacks", e);
474             }
475 
476         } else {
477             mGestureNavigationSettingsObserver.register();
478             updateDisplaySize();
479             if (DEBUG_MISSING_GESTURE) {
480                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
481             }
482             TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
483             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
484                     mMainExecutor::execute, mOnPropertiesChangedListener);
485 
486             try {
487                 mWindowManagerService.registerSystemGestureExclusionListener(
488                         mGestureExclusionListener, mDisplayId);
489             } catch (RemoteException | IllegalArgumentException e) {
490                 Log.e(TAG, "Failed to register window manager callbacks", e);
491             }
492 
493             // Register input event receiver
494             mInputMonitor = InputManager.getInstance().monitorGestureInput(
495                     "edge-swipe", mDisplayId);
496             mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
497                     mInputMonitor.getInputChannel(), Looper.getMainLooper(),
498                     Choreographer.getInstance(), this::onInputEvent);
499 
500             // Add a nav bar panel window
501             setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
502             mPluginManager.addPluginListener(
503                     this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
504         }
505         // Update the ML model resources.
506         updateMLModelState();
507     }
508 
509     @Override
onPluginConnected(NavigationEdgeBackPlugin plugin, Context context)510     public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) {
511         setEdgeBackPlugin(plugin);
512     }
513 
514     @Override
onPluginDisconnected(NavigationEdgeBackPlugin plugin)515     public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) {
516         setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
517     }
518 
setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin)519     private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
520         if (mEdgeBackPlugin != null) {
521             mEdgeBackPlugin.onDestroy();
522         }
523         mEdgeBackPlugin = edgeBackPlugin;
524         mEdgeBackPlugin.setBackCallback(mBackCallback);
525         mEdgeBackPlugin.setLayoutParams(createLayoutParams());
526         updateDisplaySize();
527     }
528 
isHandlingGestures()529     public boolean isHandlingGestures() {
530         return mIsEnabled && mIsBackGestureAllowed;
531     }
532 
533     /**
534      * Update the PiP bounds, used for exclusion calculation.
535      */
setPipStashExclusionBounds(Rect bounds)536     public void setPipStashExclusionBounds(Rect bounds) {
537         mPipExcludedBounds.set(bounds);
538     }
539 
createLayoutParams()540     private WindowManager.LayoutParams createLayoutParams() {
541         Resources resources = mContext.getResources();
542         WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
543                 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
544                 resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
545                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
546                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
547                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
548                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
549                 PixelFormat.TRANSLUCENT);
550         layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
551         layoutParams.windowAnimations = 0;
552         layoutParams.privateFlags |=
553                 (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
554                 | PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION);
555         layoutParams.setTitle(TAG + mContext.getDisplayId());
556         layoutParams.setFitInsetsTypes(0 /* types */);
557         layoutParams.setTrustedOverlay();
558         return layoutParams;
559     }
560 
onInputEvent(InputEvent ev)561     private void onInputEvent(InputEvent ev) {
562         if (!(ev instanceof MotionEvent)) return;
563         MotionEvent event = (MotionEvent) ev;
564         if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
565             final Display display = mContext.getDisplay();
566             int rotation = display.getRotation();
567             if (rotation != Surface.ROTATION_0) {
568                 Point sz = new Point();
569                 display.getRealSize(sz);
570                 event = MotionEvent.obtain(event);
571                 event.transform(MotionEvent.createRotateMatrix(rotation, sz.x, sz.y));
572             }
573         }
574         onMotionEvent(event);
575     }
576 
updateMLModelState()577     private void updateMLModelState() {
578         boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
579                 SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
580 
581         if (newState == mUseMLModel) {
582             return;
583         }
584 
585         if (newState) {
586             String mlModelName = DeviceConfig.getString(DeviceConfig.NAMESPACE_SYSTEMUI,
587                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME, "backgesture");
588             mBackGestureTfClassifierProvider = SystemUIFactory.getInstance()
589                     .createBackGestureTfClassifierProvider(mContext.getAssets(), mlModelName);
590             mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
591                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
592             if (mBackGestureTfClassifierProvider.isActive()) {
593                 mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets());
594                 mUseMLModel = true;
595                 return;
596             }
597         }
598 
599         mUseMLModel = false;
600         if (mBackGestureTfClassifierProvider != null) {
601             mBackGestureTfClassifierProvider.release();
602             mBackGestureTfClassifierProvider = null;
603         }
604     }
605 
getBackGesturePredictionsCategory(int x, int y, int app)606     private int getBackGesturePredictionsCategory(int x, int y, int app) {
607         if (app == -1) {
608             return -1;
609         }
610         int distanceFromEdge;
611         int location;
612         if (x <= mDisplaySize.x / 2.0) {
613             location = 1;  // left
614             distanceFromEdge = x;
615         } else {
616             location = 2;  // right
617             distanceFromEdge = mDisplaySize.x - x;
618         }
619 
620         Object[] featuresVector = {
621             new long[]{(long) mDisplaySize.x},
622             new long[]{(long) distanceFromEdge},
623             new long[]{(long) location},
624             new long[]{(long) app},
625             new long[]{(long) y},
626         };
627 
628         mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector);
629         if (mMLResults == -1) {
630             return -1;
631         }
632         return mMLResults >= mMLModelThreshold ? 1 : 0;
633     }
634 
isWithinInsets(int x, int y)635     private boolean isWithinInsets(int x, int y) {
636         // Disallow if we are in the bottom gesture area
637         if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
638             return false;
639         }
640         // If the point is way too far (twice the margin), it is
641         // not interesting to us for logging purposes, nor we
642         // should process it.  Simply return false and keep
643         // mLogGesture = false.
644         if (x > 2 * (mEdgeWidthLeft + mLeftInset)
645                 && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) {
646             return false;
647         }
648         return true;
649     }
650 
isWithinTouchRegion(int x, int y)651     private boolean isWithinTouchRegion(int x, int y) {
652         // If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back
653         // gesture
654         final boolean isInsidePip = mIsInPipMode && mPipExcludedBounds.contains(x, y);
655         if (isInsidePip || mNavBarOverlayExcludedBounds.contains(x, y)) {
656             return false;
657         }
658 
659         int app = -1;
660         if (mVocab != null) {
661             app = mVocab.getOrDefault(mPackageName, -1);
662         }
663 
664         // Denotes whether we should proceed with the gesture. Even if it is false, we may want to
665         // log it assuming it is not invalid due to exclusion.
666         boolean withinRange = x < mEdgeWidthLeft + mLeftInset
667                 || x >= (mDisplaySize.x - mEdgeWidthRight - mRightInset);
668         if (withinRange) {
669             int results = -1;
670 
671             // Check if we are within the tightest bounds beyond which we would not need to run the
672             // ML model
673             boolean withinMinRange = x < mMLEnableWidth + mLeftInset
674                     || x >= (mDisplaySize.x - mMLEnableWidth - mRightInset);
675             if (!withinMinRange && mUseMLModel
676                     && (results = getBackGesturePredictionsCategory(x, y, app)) != -1) {
677                 withinRange = (results == 1);
678             }
679         }
680 
681         // For debugging purposes
682         mPredictionLog.log(String.format("Prediction [%d,%d,%d,%d,%f,%d]",
683                 System.currentTimeMillis(), x, y, app, mMLResults, withinRange ? 1 : 0));
684 
685         // Always allow if the user is in a transient sticky immersive state
686         if (mIsNavBarShownTransiently) {
687             mLogGesture = true;
688             return withinRange;
689         }
690 
691         if (mExcludeRegion.contains(x, y)) {
692             if (withinRange) {
693                 // Log as exclusion only if it is in acceptable range in the first place.
694                 mOverviewProxyService.notifyBackAction(
695                         false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
696                 // We don't have the end point for logging purposes.
697                 mEndPoint.x = -1;
698                 mEndPoint.y = -1;
699                 mLogGesture = true;
700                 logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_EXCLUDED);
701             }
702             return false;
703         }
704 
705         mInRejectedExclusion = mUnrestrictedExcludeRegion.contains(x, y);
706         mLogGesture = true;
707         return withinRange;
708     }
709 
cancelGesture(MotionEvent ev)710     private void cancelGesture(MotionEvent ev) {
711         // Send action cancel to reset all the touch events
712         mAllowGesture = false;
713         mLogGesture = false;
714         mInRejectedExclusion = false;
715         MotionEvent cancelEv = MotionEvent.obtain(ev);
716         cancelEv.setAction(MotionEvent.ACTION_CANCEL);
717         mEdgeBackPlugin.onMotionEvent(cancelEv);
718         cancelEv.recycle();
719     }
720 
logGesture(int backType)721     private void logGesture(int backType) {
722         if (!mLogGesture) {
723             return;
724         }
725         mLogGesture = false;
726         String logPackageName = "";
727         // Due to privacy, only top 100 most used apps by all users can be logged.
728         if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) {
729             logPackageName = mPackageName;
730         }
731         SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType,
732                 (int) mDownPoint.y, mIsOnLeftEdge
733                         ? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT
734                         : SysUiStatsLog.BACK_GESTURE__X_LOCATION__RIGHT,
735                 (int) mDownPoint.x, (int) mDownPoint.y,
736                 (int) mEndPoint.x, (int) mEndPoint.y,
737                 mEdgeWidthLeft + mLeftInset,
738                 mDisplaySize.x - (mEdgeWidthRight + mRightInset),
739                 mUseMLModel ? mMLResults : -2, logPackageName);
740     }
741 
onMotionEvent(MotionEvent ev)742     private void onMotionEvent(MotionEvent ev) {
743         int action = ev.getActionMasked();
744         if (action == MotionEvent.ACTION_DOWN) {
745             if (DEBUG_MISSING_GESTURE) {
746                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
747             }
748 
749             // Verify if this is in within the touch region and we aren't in immersive mode, and
750             // either the bouncer is showing or the notification panel is hidden
751             mInputEventReceiver.setBatchingEnabled(false);
752             mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
753             mMLResults = 0;
754             mLogGesture = false;
755             mInRejectedExclusion = false;
756             boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
757             mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets
758                     && !mGestureBlockingActivityRunning
759                     && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
760                     && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
761             if (mAllowGesture) {
762                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
763                 mEdgeBackPlugin.onMotionEvent(ev);
764             }
765             if (mLogGesture) {
766                 mDownPoint.set(ev.getX(), ev.getY());
767                 mEndPoint.set(-1, -1);
768                 mThresholdCrossed = false;
769             }
770 
771             // For debugging purposes, only log edge points
772             (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
773                     "Gesture [%d,alw=%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]",
774                     System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge,
775                     mIsBackGestureAllowed,
776                     QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize,
777                     mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
778         } else if (mAllowGesture || mLogGesture) {
779             if (!mThresholdCrossed) {
780                 mEndPoint.x = (int) ev.getX();
781                 mEndPoint.y = (int) ev.getY();
782                 if (action == MotionEvent.ACTION_POINTER_DOWN) {
783                     if (mAllowGesture) {
784                         logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
785                         if (DEBUG_MISSING_GESTURE) {
786                             Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back: multitouch");
787                         }
788                         // We do not support multi touch for back gesture
789                         cancelGesture(ev);
790                     }
791                     mLogGesture = false;
792                     return;
793                 } else if (action == MotionEvent.ACTION_MOVE) {
794                     if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
795                         if (mAllowGesture) {
796                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS);
797                             cancelGesture(ev);
798                             if (DEBUG_MISSING_GESTURE) {
799                                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back [longpress]: "
800                                         + ev.getEventTime()
801                                         + "  " + ev.getDownTime()
802                                         + "  " + mLongPressTimeout);
803                             }
804                         }
805                         mLogGesture = false;
806                         return;
807                     }
808                     float dx = Math.abs(ev.getX() - mDownPoint.x);
809                     float dy = Math.abs(ev.getY() - mDownPoint.y);
810                     if (dy > dx && dy > mTouchSlop) {
811                         if (mAllowGesture) {
812                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE);
813                             cancelGesture(ev);
814                             if (DEBUG_MISSING_GESTURE) {
815                                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Cancel back [vertical move]: "
816                                         + dy + "  " + dx + "  " + mTouchSlop);
817                             }
818                         }
819                         mLogGesture = false;
820                         return;
821                     } else if (dx > dy && dx > mTouchSlop) {
822                         if (mAllowGesture) {
823                             mThresholdCrossed = true;
824                             // Capture inputs
825                             mInputMonitor.pilferPointers();
826                             mInputEventReceiver.setBatchingEnabled(true);
827                         } else {
828                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE);
829                         }
830                     }
831                 }
832             }
833 
834             if (mAllowGesture) {
835                 // forward touch
836                 mEdgeBackPlugin.onMotionEvent(ev);
837             }
838         }
839 
840         mProtoTracer.scheduleFrameUpdate();
841     }
842 
updateDisabledForQuickstep(Configuration newConfig)843     private void updateDisabledForQuickstep(Configuration newConfig) {
844         int rotation = newConfig.windowConfiguration.getRotation();
845         mDisabledForQuickstep = mStartingQuickstepRotation > -1 &&
846                 mStartingQuickstepRotation != rotation;
847     }
848 
onConfigurationChanged(Configuration newConfig)849     public void onConfigurationChanged(Configuration newConfig) {
850         if (mStartingQuickstepRotation > -1) {
851             updateDisabledForQuickstep(newConfig);
852         }
853 
854         if (DEBUG_MISSING_GESTURE) {
855             Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: config=" + newConfig);
856         }
857         updateDisplaySize();
858     }
859 
updateDisplaySize()860     private void updateDisplaySize() {
861         WindowMetrics metrics = mWindowManager.getMaximumWindowMetrics();
862         Rect bounds = metrics.getBounds();
863         mDisplaySize.set(bounds.width(), bounds.height());
864         if (DEBUG_MISSING_GESTURE) {
865             Log.d(DEBUG_MISSING_GESTURE_TAG, "Update display size: mDisplaySize=" + mDisplaySize);
866         }
867         if (mEdgeBackPlugin != null) {
868             mEdgeBackPlugin.setDisplaySize(mDisplaySize);
869         }
870     }
871 
sendEvent(int action, int code)872     private boolean sendEvent(int action, int code) {
873         long when = SystemClock.uptimeMillis();
874         final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
875                 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
876                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
877                 InputDevice.SOURCE_KEYBOARD);
878 
879         ev.setDisplayId(mContext.getDisplay().getDisplayId());
880         return InputManager.getInstance()
881                 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
882     }
883 
setInsets(int leftInset, int rightInset)884     public void setInsets(int leftInset, int rightInset) {
885         mLeftInset = leftInset;
886         mRightInset = rightInset;
887         if (mEdgeBackPlugin != null) {
888             mEdgeBackPlugin.setInsets(leftInset, rightInset);
889         }
890     }
891 
dump(PrintWriter pw)892     public void dump(PrintWriter pw) {
893         pw.println("EdgeBackGestureHandler:");
894         pw.println("  mIsEnabled=" + mIsEnabled);
895         pw.println("  mIsAttached=" + mIsAttached);
896         pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
897         pw.println("  mIsGesturalModeEnabled=" + mIsGesturalModeEnabled);
898         pw.println("  mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
899         pw.println("  mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
900         pw.println("  mAllowGesture=" + mAllowGesture);
901         pw.println("  mUseMLModel=" + mUseMLModel);
902         pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
903         pw.println("  mStartingQuickstepRotation=" + mStartingQuickstepRotation);
904         pw.println("  mInRejectedExclusion=" + mInRejectedExclusion);
905         pw.println("  mExcludeRegion=" + mExcludeRegion);
906         pw.println("  mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
907         pw.println("  mIsInPipMode=" + mIsInPipMode);
908         pw.println("  mPipExcludedBounds=" + mPipExcludedBounds);
909         pw.println("  mNavBarOverlayExcludedBounds=" + mNavBarOverlayExcludedBounds);
910         pw.println("  mEdgeWidthLeft=" + mEdgeWidthLeft);
911         pw.println("  mEdgeWidthRight=" + mEdgeWidthRight);
912         pw.println("  mLeftInset=" + mLeftInset);
913         pw.println("  mRightInset=" + mRightInset);
914         pw.println("  mMLEnableWidth=" + mMLEnableWidth);
915         pw.println("  mMLModelThreshold=" + mMLModelThreshold);
916         pw.println("  mTouchSlop=" + mTouchSlop);
917         pw.println("  mBottomGestureHeight=" + mBottomGestureHeight);
918         pw.println("  mPredictionLog=" + String.join("\n", mPredictionLog));
919         pw.println("  mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets));
920         pw.println("  mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets));
921         pw.println("  mEdgeBackPlugin=" + mEdgeBackPlugin);
922         if (mEdgeBackPlugin != null) {
923             mEdgeBackPlugin.dump(pw);
924         }
925     }
926 
isGestureBlockingActivityRunning()927     private boolean isGestureBlockingActivityRunning() {
928         ActivityManager.RunningTaskInfo runningTask =
929                 ActivityManagerWrapper.getInstance().getRunningTask();
930         ComponentName topActivity = runningTask == null ? null : runningTask.topActivity;
931         if (topActivity != null) {
932             mPackageName = topActivity.getPackageName();
933         } else {
934             mPackageName = "_UNKNOWN";
935         }
936         return topActivity != null && mGestureBlockingActivities.contains(topActivity);
937     }
938 
939     @Override
writeToProto(SystemUiTraceProto proto)940     public void writeToProto(SystemUiTraceProto proto) {
941         if (proto.edgeBackGestureHandler == null) {
942             proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto();
943         }
944         proto.edgeBackGestureHandler.allowGesture = mAllowGesture;
945     }
946 
947     /**
948      * Injectable instance to create a new EdgeBackGestureHandler.
949      *
950      * Necessary because we don't have good handling of per-display contexts at the moment. With
951      * this, you can pass in a specific context that knows what display it is in.
952      */
953     public static class Factory {
954         private final OverviewProxyService mOverviewProxyService;
955         private final SysUiState mSysUiState;
956         private final PluginManager mPluginManager;
957         private final Executor mExecutor;
958         private final BroadcastDispatcher mBroadcastDispatcher;
959         private final ProtoTracer mProtoTracer;
960         private final NavigationModeController mNavigationModeController;
961         private final ViewConfiguration mViewConfiguration;
962         private final WindowManager mWindowManager;
963         private final IWindowManager mWindowManagerService;
964         private final FalsingManager mFalsingManager;
965 
966         @Inject
Factory(OverviewProxyService overviewProxyService, SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, NavigationModeController navigationModeController, ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, FalsingManager falsingManager)967         public Factory(OverviewProxyService overviewProxyService,
968                 SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor,
969                 BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer,
970                 NavigationModeController navigationModeController,
971                 ViewConfiguration viewConfiguration, WindowManager windowManager,
972                 IWindowManager windowManagerService, FalsingManager falsingManager) {
973             mOverviewProxyService = overviewProxyService;
974             mSysUiState = sysUiState;
975             mPluginManager = pluginManager;
976             mExecutor = executor;
977             mBroadcastDispatcher = broadcastDispatcher;
978             mProtoTracer = protoTracer;
979             mNavigationModeController = navigationModeController;
980             mViewConfiguration = viewConfiguration;
981             mWindowManager = windowManager;
982             mWindowManagerService = windowManagerService;
983             mFalsingManager = falsingManager;
984         }
985 
986         /** Construct a {@link EdgeBackGestureHandler}. */
create(Context context)987         public EdgeBackGestureHandler create(Context context) {
988             return new EdgeBackGestureHandler(context, mOverviewProxyService, mSysUiState,
989                     mPluginManager, mExecutor, mBroadcastDispatcher, mProtoTracer,
990                     mNavigationModeController, mViewConfiguration, mWindowManager,
991                     mWindowManagerService, mFalsingManager);
992         }
993     }
994 
995     private static class LogArray extends ArrayDeque<String> {
996         private final int mLength;
997 
LogArray(int length)998         LogArray(int length) {
999             mLength = length;
1000         }
1001 
log(String message)1002         void log(String message) {
1003             if (size() >= mLength) {
1004                 removeFirst();
1005             }
1006             addLast(message);
1007             if (DEBUG_MISSING_GESTURE) {
1008                 Log.d(DEBUG_MISSING_GESTURE_TAG, message);
1009             }
1010         }
1011     }
1012 }
1013