实现全局双指长按返回桌面

最近做一个功能,由于我们设备上,没有功能键,所以需要实现一个功能,不管在设备上的哪个应用里,双指长按,就必须返回Launcher界面。
刚开始接这个需求,一脸懵逼,我去,这…之前都是在一个App里跳转来跳转去的,这可咋整,能咋整,不会写,那抄呗,网上一顿搜,搜到的资料很少,突然想到,全局手势返回桌面,那去安卓原生的全局手势代码那块瞅瞅呗

添加手势

///WorkSpaces/LA.UM.9.15/LINUX/android/frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
//全局手势核心代码
   @Override
    public void onPointerEvent(MotionEvent event) {
        if (mGestureDetector != null && event.isTouchEvent()) {
            mGestureDetector.onTouchEvent(event);
        }
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mSwipeFireable = true;
                mDebugFireable = true;
                mDownPointers = 0;
                captureDown(event, 0);
                if (mMouseHoveringAtEdge) {
                    mMouseHoveringAtEdge = false;
                    mCallbacks.onMouseLeaveFromEdge();
                }
                mCallbacks.onDown();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                captureDown(event, event.getActionIndex());
                if (mDebugFireable) {
                    mDebugFireable = event.getPointerCount() < 5;
                    if (!mDebugFireable) {
                        if (DEBUG) Slog.d(TAG, "Firing debug");
                        mCallbacks.onDebug();
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mSwipeFireable) {
                    final int swipe = detectSwipe(event);
                    mSwipeFireable = swipe == SWIPE_NONE;
                    if (swipe == SWIPE_FROM_TOP) {
                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
                        mCallbacks.onSwipeFromTop();
                    } else if (swipe == SWIPE_FROM_BOTTOM) {
                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
                        mCallbacks.onSwipeFromBottom();
                    } else if (swipe == SWIPE_FROM_RIGHT) {
                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
                        mCallbacks.onSwipeFromRight();
                    } else if (swipe == SWIPE_FROM_LEFT) {
                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft");
                        mCallbacks.onSwipeFromLeft();
                    }
                }
                break;
            case MotionEvent.ACTION_HOVER_MOVE:
                if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                    if (!mMouseHoveringAtEdge && event.getY() == 0) {
                        mCallbacks.onMouseHoverAtTop();
                        mMouseHoveringAtEdge = true;
                    } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) {
                        mCallbacks.onMouseHoverAtBottom();
                        mMouseHoveringAtEdge = true;
                    } else if (mMouseHoveringAtEdge
                            && (event.getY() > 0 && event.getY() < screenHeight - 1)) {
                        mCallbacks.onMouseLeaveFromEdge();
                        mMouseHoveringAtEdge = false;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mSwipeFireable = false;
                mDebugFireable = false;
                mCallbacks.onUpOrCancel();
                break;
            default:
                if (DEBUG) Slog.d(TAG, "Ignoring " + event);
        }
    }

在这里,我们看到四个回调,就是我们常见的四个全局手势

 interface Callbacks {
        void onSwipeFromTop();
        void onSwipeFromBottom();
        void onSwipeFromRight();
        void onSwipeFromLeft();
   }

然后我们可以在这里看到他们的实现

//frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
 DisplayPolicy(WindowManagerService service, DisplayContent displayContent) {
      //.................
        mSystemGestures = new SystemGesturesPointerEventListener(mContext, mHandler,
                new SystemGesturesPointerEventListener.Callbacks() {
                    @Override
                    public void onSwipeFromTop() {
                        synchronized (mLock) {
                            if (mStatusBar != null) {
                                requestTransientBars(mStatusBar);
                            }
                        }
                    }

                    @Override
                    public void onSwipeFromBottom() {
                        synchronized (mLock) {
                            if (mNavigationBar != null
                                    && mNavigationBarPosition == NAV_BAR_BOTTOM) {
                                requestTransientBars(mNavigationBar);
                            }
                        }
                    }

                    @Override
                    public void onSwipeFromRight() {
                        final Region excludedRegion = Region.obtain();
                        synchronized (mLock) {
                            mDisplayContent.calculateSystemGestureExclusion(
                                    excludedRegion, null /* outUnrestricted */);
                            final boolean sideAllowed = mNavigationBarAlwaysShowOnSideGesture
                                    || mNavigationBarPosition == NAV_BAR_RIGHT;
                            if (mNavigationBar != null && sideAllowed
                                    && !mSystemGestures.currentGestureStartedInRegion(
                                            excludedRegion)) {
                                requestTransientBars(mNavigationBar);
                            }
                        }
                        excludedRegion.recycle();
                    }

                    @Override
                    public void onSwipeFromLeft() {
                        final Region excludedRegion = Region.obtain();
                        synchronized (mLock) {
                            mDisplayContent.calculateSystemGestureExclusion(
                                    excludedRegion, null /* outUnrestricted */);
                            final boolean sideAllowed = mNavigationBarAlwaysShowOnSideGesture
                                    || mNavigationBarPosition == NAV_BAR_LEFT;
                            if (mNavigationBar != null && sideAllowed
                                    && !mSystemGestures.currentGestureStartedInRegion(
                                            excludedRegion)) {
                                requestTransientBars(mNavigationBar);
                            }
                        }
                        excludedRegion.recycle();
                    }

                    @Override
                    public void onFling(int duration) {
                        if (mService.mPowerManagerInternal != null) {
                            mService.mPowerManagerInternal.powerHint(
                                    PowerHint.INTERACTION, duration);
                        }
                    }


                });
     //...................
    }

这里是对他们的具体实现的地方,所以如果我们的需求是上划返回桌面,那么只需在onSwipeFromBottom做返回操作就可以了。不过我们的需求是双指长按…
不对,我们刚才好像看到啥了,回过头去看看

///WorkSpaces/LA.UM.9.15/LINUX/android/frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
   @Override
    public void onPointerEvent(MotionEvent event) {
        if (mGestureDetector != null && event.isTouchEvent()) {
            mGestureDetector.onTouchEvent(event);
        }
        //.......................
   }
   //继续往下看
   public void systemReady() {
        // GestureDetector records statistics about gesture classification events to inform gesture
        // usage trends. SystemGesturesPointerEventListener creates a lot of noise in these
        // statistics because it passes every touch event though a GestureDetector. By creating an
        // anonymous subclass of GestureDetector, these statistics will be recorded with a unique
        // source name that can be filtered.

        // GestureDetector would get a ViewConfiguration instance by context, that may also
        // create a new WindowManagerImpl for the new display, and lock WindowManagerGlobal
        // temporarily in the constructor that would make a deadlock.
        mHandler.post(() -> {
            final int displayId = mContext.getDisplayId();
            final DisplayInfo info = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
            if (info == null) {
                // Display already removed, stop here.
                Slog.w(TAG, "Cannot create GestureDetector, display removed:" + displayId);
                return;
            }
            mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler) {
            };
        });
    }

    private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {

        private OverScroller mOverscroller;

        FlingGestureDetector() {
            mOverscroller = new OverScroller(mContext);
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            if (!mOverscroller.isFinished()) {
                mOverscroller.forceFinished(true);
            }
            return true;
        }
        @Override
        public boolean onFling(MotionEvent down, MotionEvent up,
                float velocityX, float velocityY) {
        //............
            return true;
        }
    }
    ///frameworks/base/core/java/android/view/GestureDetector.java
   public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
            OnContextClickListener {

        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }

        public void onLongPress(MotionEvent e) {
        }

        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
            return false;
        }

        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                float velocityY) {
            return false;
        }

        public void onShowPress(MotionEvent e) {
        }

        public boolean onDown(MotionEvent e) {
            return false;
        }

        public boolean onDoubleTap(MotionEvent e) {
            return false;
        }

        public boolean onDoubleTapEvent(MotionEvent e) {
            return false;
        }

        public boolean onSingleTapConfirmed(MotionEvent e) {
            return false;
        }

        public boolean onContextClick(MotionEvent e) {
            return false;
        }
    }

可以看到底层已经实现好的一些方法比如单机,双击等供我们使用。
灵机一动,那我们在GestureDetector里实现双指长按的逻辑,然后在上面FlingGestureDetector里调用双指长按,在它里面去实现回到桌面的逻辑,不就OK了。
给SimpleOnGestureListener添加双指长按事件,然后在GestureDetector的onTouchEvent方法里,做双指长按事件触发逻辑,大概如下

    private void handlePointerDown(MotionEvent ev, Float focusX) {
        Log.d(TAG, "handlePointerDown");
        mDoubleFingerPressStartMs = SystemClock.elapsedRealtime();
        if (ev.getPointerCount() != 2 || !isDoubleFingerEvent()) {
            return;
        }
        mDownFocusX = focusX;
        if (mCurrentPointerDownEvent != null) {
            mCurrentPointerDownEvent.recycle();
        }
        mCurrentPointerDownEvent = MotionEvent.obtain(ev);
        mHandler.removeMessages(DOUBLE_FINGER_PRESS);
        mHandler.sendEmptyMessageAtTime(DOUBLE_FINGER_PRESS, SystemClock.uptimeMillis() + 1000);
    }

    private boolean isDoubleFingerEvent() {
        return mDoubleFingerPressStartMs - mSingleFingerPressStartMs <= 100;
    }


    private void handlePointerUp(MotionEvent ev) {
        Log.d(TAG, "handlePointerUp");
        if (ev.getPointerCount() != 2 || !isDoubleFingerEvent()) {
            return;
        }
        mHandler.removeMessages(DOUBLE_FINGER_PRESS);
    }

而在SystemGesturesPointerEventListener类的FlingGestureDetector这边实现添加的双指长按方法:

    @Override
        public boolean onDoubleFingerLongPress(MotionEvent e) {
            if (DEBUG) Slog.d(TAG, "onDoubleFingerLongPress");
            return true;
        }

编译出包,刷进去之后,双指长按,发现日志顺利打出来,接下来,我们要考虑如何回到桌面

返回桌面

这个就容易多了,我们home键,或者back键都可以返回,但是back键的话,如果应用进入二级页面,那么就得执行两次双指长按操作,不符合需求。所以肯定考虑home事件。
参考

 private void sendKey(int keyCode) {
            try {
                int inputSource = KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY | InputDevice.SOURCE_KEYBOARD;
                long now = SystemClock.uptimeMillis();
                boolean istrue = InputManager.getInstance().injectInputEvent(
                        new KeyEvent(
                                now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
                                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource
                        ), InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
                );
                boolean isok = InputManager.getInstance().injectInputEvent(
                        new KeyEvent(
                                now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
                                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource
                        ), InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
                );
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
  //所以上面我们只需要这样调用就可以返回桌面了。
     @Override
        public boolean onDoubleFingerLongPress(MotionEvent e) {
            if (DEBUG) Slog.d(TAG, "onDoubleFingerLongPress");
            sendKey(KeyEvent.KEYCODE_HOME);
            return true;
        }

总结: Framework代码,还是多看,看看优秀的人是咋写的
看完之后呢,照猫画虎哈,没事,慢慢就好了。

你可能感兴趣的:(android,Framework分析,android,java,开发语言)