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