Android registration screen touch event interception event distribution

Android registration screen touch event interception event distribution

  • Preface
  • Native sliding return gesture interception distribution analysis
  • end

Foreword

After receiving the demand, it is necessary to realize the corresponding function to be triggered by sliding up and down with two fingers on the screen, and to intercept sliding conflicts;
Currently, many types of functions can be seen in the world, such as side swiping to return, three-finger swiping to take screenshots, etc.;
This time it is based on Android10, and the application is a system application!
No source code for three-finger sliding has been found in the native code, but fortunately there is a side-swipe return to the source code. When used on the machine, it can be seen that sliding conflicts are handled.
This article is based on Android10 source code

Native sliding return gesture interception distribution analysis

Source code link: http://aospxref.com/android-10.0.0_r47/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java

Mainly look at the updateIsEnabled method

 private void updateIsEnabled() {<!-- -->
        boolean isEnabled = mIsAttached & amp; & amp; mIsGesturalModeEnabled;
        if (isEnabled == mIsEnabled) {<!-- -->
            return;
        }
        mIsEnabled = isEnabled;
        disposeInputChannel();

        if (mEdgePanel != null) {<!-- -->
            mWm.removeView(mEdgePanel);
            mEdgePanel = null;
            mRegionSamplingHelper.stop();
            mRegionSamplingHelper = null;
        }

        if (!mIsEnabled) {<!-- -->
            WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener);
            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);

            try {<!-- -->
                WindowManagerGlobal.getWindowManagerService()
                        .unregisterSystemGestureExclusionListener(
                                mGestureExclusionListener, mDisplayId);
            } catch (RemoteException e) {<!-- -->
                Log.e(TAG, "Failed to unregister window manager callbacks", e);
            }

        } else {<!-- -->
            updateDisplaySize();
            //#1 Register screen events
            mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
                    mContext.getMainThreadHandler());

            try {<!-- -->
                WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener);
                WindowManagerGlobal.getWindowManagerService()
                        .registerSystemGestureExclusionListener(
                                mGestureExclusionListener, mDisplayId);
            } catch (RemoteException e) {<!-- -->
                Log.e(TAG, "Failed to register window manager callbacks", e);
            }

//#2 Register screen touch event
            // Register input event receiver
            mInputMonitor = InputManager.getInstance().monitorGestureInput(
                    "edge-swipe", mDisplayId);
            mInputEventReceiver = new SysUiInputEventReceiver(
                    mInputMonitor.getInputChannel(), Looper.getMainLooper());

            // Add a nav bar panel window
            mEdgePanel = new NavigationBarEdgePanel(mContext);
            mEdgePanelLp = new WindowManager.LayoutParams(
                    mContext.getResources()
                            .getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
                    mContext.getResources()
                            .getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                    PixelFormat.TRANSLUCENT);
            mEdgePanelLp.privateFlags |=
                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
            mEdgePanelLp.setTitle(TAG + mDisplayId);
            mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
            mEdgePanelLp.windowAnimations = 0;
            mEdgePanel.setLayoutParams(mEdgePanelLp);
            mWm.addView(mEdgePanel, mEdgePanelLp);
            mRegionSamplingHelper = new RegionSamplingHelper(mEdgePanel,
                    new RegionSamplingHelper.SamplingCallback() {<!-- -->
                        @Override
                        public void onRegionDarknessChanged(boolean isRegionDark) {<!-- -->
                            mEdgePanel.setIsDark(!isRegionDark, true /* animate */);
                        }

                        @Override
                        public Rect getSampledRegion(View sampledView) {<!-- -->
                            return mSamplingRect;
                        }
                    });
        }
    }

//...omit part of the code

private void onInputEvent(InputEvent ev) {<!-- -->
        if (ev instanceof MotionEvent) {<!-- -->
            onMotionEvent((MotionEvent) ev);
        }
    }

    private boolean isWithinTouchRegion(int x, int y) {<!-- -->
        if (y > (mDisplaySize.y - Math.max(mImeHeight, mNavBarHeight))) {<!-- -->
            return false;
        }

        if (x > mEdgeWidth + mLeftInset & amp; & amp; x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {<!-- -->
            return false;
        }
        boolean isInExcludedRegion = mExcludeRegion.contains(x, y);
        if (isInExcludedRegion) {<!-- -->
            mOverviewProxyService.notifyBackAction(false /* completed */, -1, -1,
                    false /* isButton */, !mIsOnLeftEdge);
        }
        return !isInExcludedRegion;
    }

    private void cancelGesture(MotionEvent ev) {<!-- -->
        // Send action cancel to reset all the touch events
        mAllowGesture = false;
        MotionEvent cancelEv = MotionEvent.obtain(ev);
        cancelEv.setAction(MotionEvent.ACTION_CANCEL);
        mEdgePanel.handleTouch(cancelEv);
        cancelEv.recycle();
    }

    private void onMotionEvent(MotionEvent ev) {<!-- -->
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {<!-- -->
            // Verify if this is in within the touch region and we aren't in immersive mode, and
            // either the bouncer is showing or the notification panel is hidden
            int stateFlags = mOverviewProxyService.getSystemUiStateFlags();
            mIsOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
            mAllowGesture = !QuickStepContract.isBackGestureDisabled(stateFlags)
                     & amp; & amp; isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
            if (mAllowGesture) {<!-- -->
                mEdgePanelLp.gravity = mIsOnLeftEdge
                        ? (Gravity.LEFT | Gravity.TOP)
                        : (Gravity.RIGHT | Gravity.TOP);
                mEdgePanel.setIsLeftPanel(mIsOnLeftEdge);
                mEdgePanel.handleTouch(ev);
                updateEdgePanelPosition(ev.getY());
                mWm.updateViewLayout(mEdgePanel, mEdgePanelLp);
                mRegionSamplingHelper.start(mSamplingRect);

                mDownPoint.set(ev.getX(), ev.getY());
                mThresholdCrossed = false;
            }
        } else if (mAllowGesture) {<!-- -->
            if (!mThresholdCrossed) {<!-- -->
                if (action == MotionEvent.ACTION_POINTER_DOWN) {<!-- -->
                    // We do not support multi touch for back gesture
                    // Cancel the gesture after multi-finger touch. Experiment with the phone and you can see that the event will be canceled when we add another finger when sliding with one finger.
                    cancelGesture(ev);
                    return;
                } else if (action == MotionEvent.ACTION_MOVE) {<!-- -->
                    if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {<!-- -->
                        cancelGesture(ev);
                        return;
                    }
                    float dx = Math.abs(ev.getX() - mDownPoint.x);
                    float dy = Math.abs(ev.getY() - mDownPoint.y);
                    if (dy > dx & amp; & amp; dy > mTouchSlop) {<!-- -->
                    //If you slide vertically, cancel the event
                        cancelGesture(ev);
                        return;

                    } else if (dx > dy & amp; & amp; dx > mTouchSlop) {<!-- -->
                        mThresholdCrossed = true;
                        // Capture inputs
                        // Capture the current gesture to prevent interference with the interface
                        mInputMonitor.pilferPointers();
                    }
                }

            }

            // forward touch
            mEdgePanel.handleTouch(ev);

            boolean isUp = action == MotionEvent.ACTION_UP;
            if (isUp) {<!-- -->
                boolean performAction = mEdgePanel.shouldTriggerBack();
                if (performAction) {<!-- -->
                    // Perform back
                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
                }
                mOverviewProxyService.notifyBackAction(performAction, (int) mDownPoint.x,
                        (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
            }
            if (isUp || action == MotionEvent.ACTION_CANCEL) {<!-- -->
                mRegionSamplingHelper.stop();
            } else {<!-- -->
                updateSamplingRect();
                mRegionSamplingHelper.updateSamplingRect();
            }
        }
    }
\t
    class SysUiInputEventReceiver extends InputEventReceiver {<!-- -->
        SysUiInputEventReceiver(InputChannel channel, Looper looper) {<!-- -->
            super(channel, looper);
        }

        public void onInputEvent(InputEvent event) {<!-- -->
            EdgeBackGestureHandler.this.onInputEvent(event);
            finishInputEvent(event, true);
        }
    }

1. Register screen event code

///#1 Register screen events
mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
         mContext.getMainThreadHandler());
///#2 Register screen touch events
// Register input event receiver
mInputMonitor = InputManager.getInstance().monitorGestureInput(
        "edge-swipe", mDisplayId);
mInputEventReceiver = new SysUiInputEventReceiver(
        mInputMonitor.getInputChannel(), Looper.getMainLooper());

2. Check the touch logic
The final trend of the event is onMotionEvent
You can see that it will be intercepted after it meets the side sliding gesture.

// Capture the current gesture to prevent interference with the interface
mInputMonitor.pilferPointers();

It’s almost done here. We only need to cancel and intercept according to the corresponding gesture function to realize the sliding conflict problem;

End

Thanks to Android for providing the source code
Thanks to Qiyuexiaxun for analyzing the gesture code and Android gesture navigation core implementation.

This article only provides a general idea about sliding conflicts. The article may involve errors or deficiencies. Thank you for your correction!