Android Open Source - BlueCtrl Mouse Touch Listener






From Project

Back to project page BlueCtrl.

License

The source code is released under:

GNU General Public License

If you think the Android project BlueCtrl listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2012//from w w w  .jav  a  2 s  .c  o m
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ronsdev.bluectrl.widget;

import org.ronsdev.bluectrl.DeviceSettings;
import org.ronsdev.bluectrl.HidKeyboard;
import org.ronsdev.bluectrl.HidMouse;
import org.ronsdev.bluectrl.IntArrayList;

import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.MotionEvent.PointerCoords;
import android.view.View.OnTouchListener;

/**
 * Touch event listener that controls a HID Mouse.
 */
public class MouseTouchListener implements OnTouchListener {

    private static final String TAG = "MouseTouchListener";
    private static final boolean V = false;


    /** Distance in percent from an edge of the touchpad where a gesture is detected. */
    private static final int GESTURE_EDGE_THRESHOLD_P = 10;

    /** Maximum distance from an edge of the touchpad where a gesture is detected. */
    private static final float MAX_GESTURE_EDGE_THRESHOLD_DP = 72.0f;

    /** Minimum distance to detect a gesture with a single touch point. */
    private static final float MIN_GESTURE_DISTANCE_DP = 10.0f;

    /**
     * Minimum distance to detect a gesture with multiple touch points.
     * Should be higher than the single touch equivalent because some touch screens lose accuracy
     * with multiple touch points.
     */
    private static final float MIN_MT_GESTURE_DISTANCE_DP = 35.0f;

    /** Minimum span distance to detect a pinch-to-zoom gesture. */
    private static final float MIN_PINCH_ZOOM_DISTANCE_DP = 35.0f;


    /** Maximum time (in ms) for a tap to be considered a click. */
    private static final int MAX_TAP_TOUCH_TIME = 150;

    /** Maximum time (in ms) between two taps to execute a double click or drag action. */
    private static final int MAX_TAP_GAP_TIME = 150;

    /** Maximum allowed distance for a tap with a single touch point to be considered a click. */
    private static final float MAX_TAP_DISTANCE_DP = 8.0f;

    /**
     * Maximum allowed distance for a tap with multiple touch points to be considered a click.
     * Should be higher than the single touch equivalent because some touch screens lose accuracy
     * with multiple touch points.
     */
    private static final float MAX_MT_TAP_DISTANCE_DP = 30.0f;


    /**
     * Distance from an edge of the touchpad where the pointer movement is continued automatically.
     */
    private static final float POINTER_EDGE_MOVE_THRESHOLD_DP = 18.0f;

    /** Pointer movement speed when the touch point is at the edge of the touchpad. */
    private static final float POINTER_EDGE_MOVE_STEP = 4.0f;

    /** Faster pointer movement speed when the touch point is at the edge of the touchpad. */
    private static final float FAST_POINTER_EDGE_MOVE_STEP = 20.0f;


    /**
     * Maximum time (in ms) the pointer movement can be interrupted when a touch end was predicted.
     */
    private static final int MAX_TOUCH_END_PREDICT_TIME = 50;

    /**
     * Maximum allowed distance the pointer movement can be interrupted when a touch end was
     * predicted.
     */
    private static final float MAX_TOUCH_END_PREDICT_DISTANCE_DP = 5.0f;


    /** Intermediate step count of the Smooth Scroll feature. */
    private static final int SMOOTH_SCROLL_STEPS = 16;

    /** Minimum touch move distance per millisecond that is required to change the scroll mode. */
    private static final float CHANGE_SCROLL_MODE_THRESHOLD_DP = 0.3f;


    /** Repeat time (in ms) for the fling scroll loop. */
    private static final int FLING_SCROLL_LOOP_TIME = 20;

    /** Minimum scroll distance that is required to start a fling scroll action. */
    private static final float FLING_SCROLL_THRESHOLD_DP = 6.0f;

    /** Minimum scroll distance that is required to retain the previous fling scroll speed. */
    private static final float MULTIPLE_FLING_SCROLL_THRESHOLD_DP = 24.0f;

    /** The amount of friction applied to the fling scroll movement. */
    private static final float FLING_SCROLL_MOVE_FRICTION_DP = 0.8f;


    private TouchpadView mTouchpadView;
    private HidMouse mHidMouse;
    private HidKeyboard mHidKeyboard;

    private Rect mTouchpadAreaRect;
    private float mMouseSensitivity;
    private float mSmoothScrollSensitivity;
    private float mStepScrollSensitivity;
    private float mPinchZoomSensitivity;
    private boolean mInvertScroll;
    private boolean mFlingScroll;


    private final float mDisplayDensity;

    private final float mMaxGestureEdgeThreshold;
    private final float mPointerEdgeMoveThreshold;
    private final float mMinGestureDistance;
    private final float mMinMultitouchGestureDistance;
    private final float mMinPinchZoomDistance;
    private final float mMaxTapDistanceSquare;
    private final float mMaxMultitouchTapDistanceSquare;
    private final float mMaxTouchEndPredictDistanceSquare;
    private final float mChangeScrollModeThreshold;
    private final float mFlingScrollThreshold;
    private final float mMultipleFlingScrollThreshold;
    private final float mFlingScrollMoveFriction;

    private IdleSubListener mIdleSubListener = new IdleSubListener();
    private GestureSubListener mGestureSubListener = new GestureSubListener();
    private TapSubListener mTapSubListener = new TapSubListener();
    private PointerSubListener mPointerSubListener = new PointerSubListener();
    private ScrollSubListener mScrollSubListener = new ScrollSubListener();
    private PinchZoomSubListener mPinchZoomSubListener = new PinchZoomSubListener();

    /** The current internal touch event listener that handles the touch events. */
    private SubListener mSubListener;

    /** A list with the currently tracked pointer IDs. */
    private IntArrayList mPointerIdList = new IntArrayList(10);

    /** The touched point from the first touch down event. */
    private PointerCoords mFirstPoint = new PointerCoords();

    /** The touched point from the second touch down event. */
    private PointerCoords mSecondPoint = new PointerCoords();

    /** The event time of the first touch down event. */
    private long mFirstEventTime = 0;

    /** The previous touched point from the controlling pointer. */
    private PointerCoords mPreviousPoint = new PointerCoords();

    /** The event time of the previous touched point. */
    private long mPreviousEventTime = 0;


    private OnScrollModeChangedListener mOnScrollModeChangedListener;
    private OnTouchpadGestureListener mOnTouchpadGestureListener;


    public MouseTouchListener(TouchpadView touchpadView) {
        mTouchpadView = touchpadView;

        mSubListener = mIdleSubListener;

        setTouchpadAreaRect(new Rect(mTouchpadView.getLeft(),
                mTouchpadView.getTop(),
                mTouchpadView.getRight(),
                mTouchpadView.getBottom()));
        setMouseSensitivity(DeviceSettings.DEFAULT_MOUSE_SENSITIVITY);
        setScrollSensitivity(DeviceSettings.DEFAULT_SCROLL_SENSITIVITY);
        setPinchZoomSensitivity(DeviceSettings.DEFAULT_PINCH_ZOOM_SENSITIVITY);
        setInvertScroll(DeviceSettings.DEFAULT_INVERT_SCROLL);
        setFlingScroll(DeviceSettings.DEFAULT_FLING_SCROLL);

        mDisplayDensity = mTouchpadView.getResources().getDisplayMetrics().density;

        mMaxGestureEdgeThreshold = (MAX_GESTURE_EDGE_THRESHOLD_DP * mDisplayDensity);

        final float gestureDistance = (MIN_GESTURE_DISTANCE_DP * mDisplayDensity);
        mMinGestureDistance = gestureDistance * gestureDistance;

        final float multitouchGestureDistance = (MIN_MT_GESTURE_DISTANCE_DP * mDisplayDensity);
        mMinMultitouchGestureDistance = multitouchGestureDistance * multitouchGestureDistance;

        mMinPinchZoomDistance = (MIN_PINCH_ZOOM_DISTANCE_DP * mDisplayDensity);

        final float tapDistance = (MAX_TAP_DISTANCE_DP * mDisplayDensity);
        mMaxTapDistanceSquare = tapDistance * tapDistance;

        final float multitouchTapDistance = (MAX_MT_TAP_DISTANCE_DP * mDisplayDensity);
        mMaxMultitouchTapDistanceSquare = multitouchTapDistance * multitouchTapDistance;

        mPointerEdgeMoveThreshold = POINTER_EDGE_MOVE_THRESHOLD_DP * mDisplayDensity;

        final float touchEndDistance = (MAX_TOUCH_END_PREDICT_DISTANCE_DP * mDisplayDensity);
        mMaxTouchEndPredictDistanceSquare = touchEndDistance * touchEndDistance;

        mChangeScrollModeThreshold = CHANGE_SCROLL_MODE_THRESHOLD_DP * mDisplayDensity;
        mFlingScrollThreshold = FLING_SCROLL_THRESHOLD_DP * mDisplayDensity;
        mMultipleFlingScrollThreshold = MULTIPLE_FLING_SCROLL_THRESHOLD_DP * mDisplayDensity;
        mFlingScrollMoveFriction = FLING_SCROLL_MOVE_FRICTION_DP * mDisplayDensity;
    }


    private static boolean isDistanceReached(float x, float y, float maxDistanceSquare) {
        return (x * x) + (y * y) > maxDistanceSquare;
    }

    private static boolean isTopEdge(Rect rect, MotionEvent event, int pointerIndex,
            float threshold) {
        return ((event.getY(pointerIndex) - rect.top) < threshold);
    }

    private static boolean isBottomEdge(Rect rect, MotionEvent event, int pointerIndex,
            float threshold) {
        return ((rect.bottom - event.getY(pointerIndex)) < threshold);
    }

    private static boolean isLeftEdge(Rect rect, MotionEvent event, int pointerIndex,
            float threshold) {
        return ((event.getX(pointerIndex) - rect.left) < threshold);
    }

    private static boolean isRightEdge(Rect rect, MotionEvent event, int pointerIndex,
            float threshold) {
        return ((rect.right - event.getX(pointerIndex)) < threshold);
    }

    private static float getSpan(PointerCoords pointA, PointerCoords pointB) {
        return getSpan(pointA.x, pointA.y, pointB.x, pointB.y);
    }

    private static float getSpan(float aX, float aY, float bX, float bY) {
        final float diffX = aX - bX;
        final float diffY = aY - bY;
        return (float)Math.sqrt((diffX * diffX) + (diffY * diffY));
    }

    private static int convertPointerCountToButtonMask(int pointerCount) {
        switch (pointerCount) {
        case 1:
            return HidMouse.BUTTON_FIRST;
        case 2:
            return HidMouse.BUTTON_SECOND;
        case 3:
            return HidMouse.BUTTON_MIDDLE;
        default:
            return 0;
        }
    }


    private boolean isActive() {
        return ((mHidMouse != null) && mHidMouse.isConnected());
    }

    public HidMouse getHidMouse() {
        return mHidMouse;
    }
    public void setHidMouse(HidMouse hidMouse) {
        mHidMouse = hidMouse;
    }

    public HidKeyboard getHidKeyboard() {
        return mHidKeyboard;
    }
    public void setHidKeyboard(HidKeyboard hidKeyboard) {
        mHidKeyboard = hidKeyboard;
    }

    public Rect getTouchpadAreaRect() {
        return mTouchpadAreaRect;
    }
    public void setTouchpadAreaRect(Rect value) {
        mTouchpadAreaRect = value;
    }

    public float getMouseSensitivity() {
        return mMouseSensitivity;
    }
    public void setMouseSensitivity(float value) {
        mMouseSensitivity = value;
    }

    public float getScrollSensitivity() {
        return mSmoothScrollSensitivity;
    }
    public void setScrollSensitivity(float value) {
        mSmoothScrollSensitivity = value;
        mStepScrollSensitivity = value / SMOOTH_SCROLL_STEPS;
    }

    public float getPinchZoomSensitivity() {
        return mPinchZoomSensitivity;
    }
    public void setPinchZoomSensitivity(float value) {
        mPinchZoomSensitivity = value;
    }

    public boolean getInvertScroll() {
        return mInvertScroll;
    }
    public void setInvertScroll(boolean value) {
        mInvertScroll = value;
    }

    public boolean getFlingScroll() {
        return mFlingScroll;
    }
    public void setFlingScroll(boolean value) {
        mFlingScroll = value;
    }

    public int getScrollMode() {
        if (mSubListener instanceof ScrollSubListener) {
            return ((ScrollSubListener)mSubListener).getScrollMode();
        } else {
            return TouchpadView.SCROLL_MODE_NONE;
        }
    }

    public void activatePointerMode() {
        changeSubListener(mPointerSubListener, null);
    }

    public void activateDragMode(int dragButton, boolean useFastEdgeMovement) {
        changeSubListener(mPointerSubListener, null);
        mPointerSubListener.setDragButton(dragButton, useFastEdgeMovement);
    }

    public void activateScrollMode(int scrollMode) {
        changeSubListener(mScrollSubListener, null);
        mScrollSubListener.setScrollMode(scrollMode);
    }

    /**
     * Changes the currently used touch listener mode and immediately redirects the touch event.
     */
    private void changeSubListener(SubListener listener, MotionEvent event) {
        if (listener != mSubListener) {
            mSubListener.stop();
            mSubListener = listener;
            mSubListener.start();

            if (V) {
                Log.v(TAG, String.format("mouse touch listener changed (%s)",
                        mSubListener.getClass().getSimpleName()));
            }

            if (event != null) {
                mSubListener.onTouch(mTouchpadView, event);
            }
        }
    }

    private void setFirstEventData(MotionEvent event, int pointerIndex) {
        event.getPointerCoords(pointerIndex, mFirstPoint);
        mFirstEventTime = event.getEventTime();
    }

    private void setPreviousEventData(MotionEvent event, int pointerIndex) {
        event.getPointerCoords(pointerIndex, mPreviousPoint);
        mPreviousEventTime = event.getEventTime();
    }

    private int getMainPointerIndex(MotionEvent event) {
        if (mPointerIdList.isEmpty()) {
            return -1;
        } else {
            return event.findPointerIndex(mPointerIdList.getValue(0));
        }
    }

    private void onScrollModeChanged(int newMode, int oldMode) {
        if (mOnScrollModeChangedListener != null) {
            mOnScrollModeChangedListener.onScrollModeChanged(newMode, oldMode);
        }
    }

    public void setOnScrollModeChangedListener(OnScrollModeChangedListener listener) {
        mOnScrollModeChangedListener = listener;
    }

    private boolean onTouchpadGesture(int gesture, int direction) {
        if (mOnTouchpadGestureListener != null) {
            return mOnTouchpadGestureListener.onTouchpadGesture(gesture, direction);
        } else {
            return false;
        }
    }

    public void setOnTouchpadGestureListener(OnTouchpadGestureListener listener) {
        mOnTouchpadGestureListener = listener;
    }


    public boolean onTouch(View view, MotionEvent event) {
        if (!isActive() || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
            changeSubListener(mIdleSubListener, null);
            mPointerIdList.clear();
            return false;
        }

        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_POINTER_DOWN:
            final int downPointerIndex = event.getActionIndex();
            final int downPointerId = event.getPointerId(downPointerIndex);

            if (mPointerIdList.isEmpty()) {
                setFirstEventData(event, downPointerIndex);
                setPreviousEventData(event, downPointerIndex);
            } else if (mPointerIdList.size() == 1) {
                event.getPointerCoords(downPointerIndex, mSecondPoint);
            }

            if (!mPointerIdList.containsValue(downPointerId)) {
                mPointerIdList.addValue(downPointerId);
            }

            mSubListener.onTouch(view, event);
            break;
        case MotionEvent.ACTION_POINTER_UP:
            final int upPointerIndex = event.getActionIndex();
            final int upPointerId = event.getPointerId(upPointerIndex);
            final int listIndex = mPointerIdList.indexOfValue(upPointerId);

            if (listIndex >= 0) {
                mPointerIdList.remove(listIndex);

                // If the controlling pointer changed, reset the previous event data
                if (listIndex == 0) {
                    final int mainPointerIndex = getMainPointerIndex(event);
                    if (mainPointerIndex >= 0) {
                        setPreviousEventData(event, mainPointerIndex);
                    }
                }

                mSubListener.onTouch(view, event);
            }
            break;
        case MotionEvent.ACTION_UP:
            if (!mPointerIdList.isEmpty()) {
                mPointerIdList.clear();

                mSubListener.onTouch(view, event);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if (!mPointerIdList.isEmpty()) {
                mSubListener.onTouch(view, event);

                final int mainPointerIndex = getMainPointerIndex(event);
                if (mainPointerIndex >= 0) {
                    setPreviousEventData(event, mainPointerIndex);
                }
            }
            break;
        }

        return true;
    }

    /**
     * Abstract class for the event listener subclasses.
     */
    private abstract class SubListener implements OnTouchListener {

        public SubListener() {
            resetMembers();
        }

        protected void start() {
            resetMembers();
        }

        protected void stop() {
        }

        protected abstract void resetMembers();

        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                onTouchPointerDown(view, event);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                onTouchPointerUp(view, event);
                break;
            case MotionEvent.ACTION_MOVE:
                onTouchMove(view, event);
                break;
            }
            return true;
        }

        protected abstract void onTouchPointerDown(View view, MotionEvent event);

        protected abstract void onTouchPointerUp(View view, MotionEvent event);

        protected abstract void onTouchMove(View view, MotionEvent event);
    }

    /**
     * Touchpad event listener that is used if no other listener is active.
     */
    private class IdleSubListener extends SubListener {

        @Override
        protected void resetMembers() {
        }

        @Override
        protected void onTouchPointerDown(View view, MotionEvent event) {
            changeSubListener(mGestureSubListener, event);
        }

        @Override
        protected void onTouchPointerUp(View view, MotionEvent event) {
        }

        @Override
        protected void onTouchMove(View view, MotionEvent event) {
        }
    }


    /**
     * Touchpad event listener that detects a gesture.
     */
    private class GestureSubListener extends SubListener {

        private float mFirstSpan;
        private int mEdgeGestureType;
        private boolean mWasHandled;


        @Override
        protected void resetMembers() {
            mFirstSpan = -1f;
            mEdgeGestureType = 0;
            mWasHandled = false;
        }

        @Override
        protected void onTouchPointerDown(View view, MotionEvent event) {
            if (mPointerIdList.size() == 1) {
                final int pointerIndex = getMainPointerIndex(event);
                if (pointerIndex >= 0) {
                    final Rect rect = mTouchpadAreaRect;
                    final float thresholdY = getEdgeThreshold(rect.height());
                    final float thresholdX = getEdgeThreshold(rect.width());

                    if (isTopEdge(rect, event, pointerIndex, thresholdY)) {
                        mEdgeGestureType = TouchpadView.GESTURE_EDGE_TOP;
                    } else if (isBottomEdge(rect, event, pointerIndex, thresholdY)) {
                        mEdgeGestureType = TouchpadView.GESTURE_EDGE_BOTTOM;
                    } else if (isLeftEdge(rect, event, pointerIndex, thresholdX)) {
                        mEdgeGestureType = TouchpadView.GESTURE_EDGE_LEFT;
                    } else if (isRightEdge(rect, event, pointerIndex, thresholdX)) {
                        mEdgeGestureType = TouchpadView.GESTURE_EDGE_RIGHT;
                    }
                }

                if (mEdgeGestureType == 0) {
                    changeSubListener(mTapSubListener, event);
                }
            }
        }

        @Override
        protected void onTouchPointerUp(View view, MotionEvent event) {
            if (mPointerIdList.isEmpty()) {
                if (mWasHandled) {
                    changeSubListener(mIdleSubListener, event);
                } else {
                    changeSubListener(mTapSubListener, event);
                }
            }
        }

        @Override
        protected void onTouchMove(View view, MotionEvent event) {
            if (mWasHandled) {
                return;
            }

            if (mPointerIdList.size() > 1) {
                mEdgeGestureType = 0;
            }

            if (checkEdgeGesture(event) ||
                    checkPinchZoomGesture(event) ||
                    checkMultiTouchGesture(event)) {
                mWasHandled = true;
            }
        }

        private float getEdgeThreshold(int totalSize) {
            final float result = (totalSize * GESTURE_EDGE_THRESHOLD_P / 100);
            if (result > mMaxGestureEdgeThreshold) {
                return mMaxGestureEdgeThreshold;
            } else {
                return result;
            }
        }

        private boolean checkEdgeGesture(MotionEvent event) {
            if (mEdgeGestureType == 0) {
                return false;
            }

            final int pointerIndex = getMainPointerIndex(event);
            if (pointerIndex < 0) {
                return false;
            }

            final float deltaX = mFirstPoint.x - event.getX(pointerIndex);
            final float deltaY = mFirstPoint.y - event.getY(pointerIndex);

            if (isDistanceReached(deltaX, deltaY, mMinGestureDistance)) {
                final int direction = getGestureDirection(deltaX, deltaY);

                if (V) Log.v(TAG, String.format("edge gesture detected (%d, %d)", mEdgeGestureType, direction));

                if (onTouchpadGesture(mEdgeGestureType, direction)) {
                    return true;
                } else {
                    changeSubListener(mPointerSubListener, event);
                }
            }

            return false;
        }

        private boolean checkPinchZoomGesture(MotionEvent event) {
            if (mPointerIdList.size() != 2) {
                return false;
            }

            final int firstPointerIndex = getMainPointerIndex(event);
            final int secondPointerIndex = event.findPointerIndex(mPointerIdList.getValue(1));
            if ((firstPointerIndex < 0) || (secondPointerIndex < 0)) {
                return false;
            }

            if (mFirstSpan < 0) {
                mFirstSpan = getSpan(mFirstPoint, mSecondPoint);
            }

            final float currentSpan = getSpan(event.getX(firstPointerIndex),
                    event.getY(firstPointerIndex),
                    event.getX(secondPointerIndex),
                    event.getY(secondPointerIndex));

            if (Math.abs(currentSpan - mFirstSpan) > mMinPinchZoomDistance) {
                if (V) Log.v(TAG, String.format("pinch-to-zoom gesture detected (%f)", (currentSpan - mFirstSpan)));

                changeSubListener(mPinchZoomSubListener, event);
                return true;
            }

            return false;
        }

        private boolean checkMultiTouchGesture(MotionEvent event) {
            if (mPointerIdList.size() < 2) {
                return false;
            }

            final int pointerIndex = getMainPointerIndex(event);
            if (pointerIndex < 0) {
                return false;
            }

            final float deltaX = mFirstPoint.x - event.getX(pointerIndex);
            final float deltaY = mFirstPoint.y - event.getY(pointerIndex);

            if (isDistanceReached(deltaX, deltaY, mMinMultitouchGestureDistance)) {
                final int direction = getGestureDirection(deltaX, deltaY);

                if (mPointerIdList.size() == 2) {
                    if (V) Log.v(TAG, String.format("two finger gesture detected (%d)", direction));

                    if (onTouchpadGesture(TouchpadView.GESTURE_2FINGER, direction)) {
                        return true;
                    }
                } else if (mPointerIdList.size() == 3) {
                    if (V) Log.v(TAG, String.format("three finger gesture detected (%d)", direction));

                    if (onTouchpadGesture(TouchpadView.GESTURE_3FINGER, direction)) {
                        return true;
                    }
                } else if (mPointerIdList.size() == 4) {
                    if (V) Log.v(TAG, String.format("four finger gesture detected (%d)", direction));

                    if (onTouchpadGesture(TouchpadView.GESTURE_4FINGER, direction)) {
                        return true;
                    }
                }
            }

            return false;
        }

        private int getGestureDirection(float deltaX, float deltaY) {
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                if (deltaX > 0) {
                    return TouchpadView.GESTURE_DIRECTION_LEFT;
                } else {
                    return TouchpadView.GESTURE_DIRECTION_RIGHT;
                }
            } else {
                if (deltaY > 0) {
                    return TouchpadView.GESTURE_DIRECTION_UP;
                } else {
                    return TouchpadView.GESTURE_DIRECTION_DOWN;
                }
            }
        }
    }


    /**
     * Touchpad event listener that detects a tap action.
     */
    private class TapSubListener extends SubListener {

        /** Maximum number of simultaneously touched points. */
        private int mMaxTouchPoints;

        /** Number of successive taps. */
        private int mTapCount;


        private final Runnable mDeferredClickRunnable = new Runnable()
        {
             @Override
             public void run() {
                 if (mTouchpadView.isShown() && isActive()) {
                     final int buttonMask = convertPointerCountToButtonMask(mMaxTouchPoints);
                     if (!mHidMouse.isButtonPressed(buttonMask)) {
                         mHidMouse.clickButton(buttonMask);
                         mTouchpadView.performButtonClickFeedback();
                     }
                 }
                 changeSubListener(mIdleSubListener, null);
             }
        };


        @Override
        protected void resetMembers() {
            mMaxTouchPoints = 1;
            mTapCount = 0;
        }

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            stopDeferredClick();

            if (mMaxTouchPoints < mPointerIdList.size()) {
                mMaxTouchPoints = mPointerIdList.size();
            }

            return super.onTouch(view, event);
        }

        @Override
        protected void onTouchPointerDown(View view, MotionEvent event) {
        }

        @Override
        protected void onTouchPointerUp(View view, MotionEvent event) {
            if (mPointerIdList.isEmpty()) {
                if (isTap(event)) {
                    mTapCount++;

                    if (mTapCount == 2) {
                        executeDoubleClick();
                        changeSubListener(mIdleSubListener, event);
                    } else {
                        startDeferredClick();
                    }
                } else {
                    changeSubListener(mIdleSubListener, event);
                }
            }
        }

        @Override
        protected void onTouchMove(View view, MotionEvent event) {
            if (!isTap(event)) {
                if (mTapCount == 1) {
                    /*
                     * Don't pass the current MotionEvent to the pointer event listener. Otherwise
                     * the pointer can move before the drag button is pressed.
                     */
                    changeSubListener(mPointerSubListener, null);

                    mPointerSubListener.setDragButton(
                            convertPointerCountToButtonMask(mMaxTouchPoints));
                } else {
                    if (mMaxTouchPoints < 2) {
                        changeSubListener(mPointerSubListener, event);
                    } else {
                        changeSubListener(mGestureSubListener, event);
                    }
                }
            }
        }

        /**
         * Checks if the touch event is a valid tap action.
         */
        private boolean isTap(MotionEvent event) {
            if (!mTouchpadAreaRect.contains((int)mFirstPoint.x, (int)mFirstPoint.y)) {
                if (V) Log.v(TAG, "invalid tap  (area)");
                return false;
            }

            if ((event.getEventTime() - mFirstEventTime) > MAX_TAP_TOUCH_TIME) {
                if (V) Log.v(TAG, "invalid tap (time)");
                return false;
            }

            final int pointerIndex = getMainPointerIndex(event);
            if (pointerIndex > -1) {
                final float deltaX = mFirstPoint.x - event.getX(pointerIndex);
                final float deltaY = mFirstPoint.y - event.getY(pointerIndex);
                float maxDistanceSquare;
                if (mPointerIdList.size() > 1) {
                    maxDistanceSquare = mMaxMultitouchTapDistanceSquare;
                } else {
                    maxDistanceSquare = mMaxTapDistanceSquare;
                }

                if (isDistanceReached(deltaX, deltaY, maxDistanceSquare)) {
                    if (V) Log.v(TAG, "invalid tap (movement)");
                    return false;
                }
            }

            return true;
        }

        private void startDeferredClick() {
            mTouchpadView.postDelayed(mDeferredClickRunnable, MAX_TAP_GAP_TIME);
        }

        private void stopDeferredClick() {
            mTouchpadView.removeCallbacks(mDeferredClickRunnable);
        }

        private void executeDoubleClick() {
            if (mHidMouse != null) {
                final int buttonMask = convertPointerCountToButtonMask(mMaxTouchPoints);
                if (!mHidMouse.isButtonPressed(buttonMask)) {
                    mHidMouse.clickButton(buttonMask);
                    mTouchpadView.performButtonClickFeedback();
                    mHidMouse.clickButton(buttonMask);
                    mTouchpadView.performButtonClickFeedback();
                }
            }
        }
    }


    /**
     * Touchpad event listener that moves the Mouse pointer.
     */
    private class PointerSubListener extends SubListener {

        /** A Mouse button that is pressed for the whole pointer movement. */
        private int mDragButton;

        /** {@code true} if the edge movement should be faster than normal. */
        private boolean mUseFastEdgeMovement;

        /** Stores the next movement on the X-axis. */
        private float mMoveX;

        /** Stores the next movement on the Y-axis. */
        private float mMoveY;

        /** See {@link PointerTouchListener#isTouchEndPredicted} */
        private boolean mIsTouchEndPredicted;

        /** The event time when a touch end was predicted. */
        private long mTouchEndPredictTime;

        /** The touched point when a touch end was predicted. */
        private PointerCoords mTouchEndPredictPoint = new PointerCoords();


        @Override
        protected void resetMembers() {
            mDragButton = 0;
            mUseFastEdgeMovement = false;
            mMoveX = 0f;
            mMoveY = 0f;
            mIsTouchEndPredicted = false;
            mTouchEndPredictTime = 0;
        }

        @Override
        protected void stop() {
            super.stop();

            if ((mHidMouse != null) && (mDragButton > 0)) {
                mHidMouse.releaseButton(mDragButton);
                mDragButton = 0;
                mTouchpadView.performButtonReleaseFeedback();
            }
        }

        @Override
        protected void onTouchPointerDown(View view, MotionEvent event) {
        }

        @Override
        protected void onTouchPointerUp(View view, MotionEvent event) {
            if (mPointerIdList.isEmpty()) {
                if ((mHidMouse != null) && (mDragButton > 0)) {
                    mHidMouse.releaseButton(mDragButton);
                    mDragButton = 0;
                    mTouchpadView.performButtonReleaseFeedback();
                }
                changeSubListener(mIdleSubListener, event);
            }
        }

        @Override
        protected void onTouchMove(View view, MotionEvent event) {
            final int pointerIndex = getMainPointerIndex(event);
            if (pointerIndex < 0) {
                return;
            }

            mMoveX += event.getX(pointerIndex) - mPreviousPoint.x;
            mMoveY += event.getY(pointerIndex) - mPreviousPoint.y;

            if ((mHidMouse != null) && !isTouchEndPredicted(event, pointerIndex)) {
                if (mDragButton > 0) {
                    addPointerEdgeMovement(view, event, pointerIndex);
                }

                final int reportMoveX = convertTouchDeltaValue(mMoveX);
                final int reportMoveY = convertTouchDeltaValue(mMoveY);
                if ((reportMoveX != 0) || (reportMoveY != 0)) {
                    mHidMouse.movePointer(reportMoveX, reportMoveY);

                    // Subtract only the actually moved value
                    mMoveX -= convertReportDeltaValue(reportMoveX);
                    mMoveY -= convertReportDeltaValue(reportMoveY);
                }
            }
        }

        public void setDragButton(int dragButton) {
            setDragButton(dragButton, false);
        }

        public void setDragButton(int dragButton, boolean useFastEdgeMovement) {
            if ((mHidMouse != null) && (dragButton != mDragButton)) {
                if (mDragButton > 0) {
                    mHidMouse.releaseButton(mDragButton);
                    mDragButton = 0;
                    mTouchpadView.performButtonReleaseFeedback();
                }
                if ((dragButton > 0) && !mHidMouse.isButtonPressed(dragButton)) {
                    mDragButton = dragButton;
                    mHidMouse.pressButton(mDragButton);
                    mTouchpadView.performButtonPressFeedback();
                }
            }

            mUseFastEdgeMovement = useFastEdgeMovement;
        }

        /** Converts the touch move value to the HID Report pointer move value. */
        private int convertTouchDeltaValue(float value) {
            return (int)(value / mDisplayDensity * mMouseSensitivity);
        }

        /** Converts the HID Report pointer move value to the touch move value. */
        private float convertReportDeltaValue(int value) {
            if (mMouseSensitivity != 0) {
                return (value * mDisplayDensity / mMouseSensitivity);
            } else {
                return 0.0f;
            }
        }

        /**
         * Adds a constant movement if the touch event is at an edge of the view.
         * This is especially useful if a drag action is active and the target hasn't been reached
         * yet.
         */
        private void addPointerEdgeMovement(View view, MotionEvent event, int pointerIndex) {
            final Rect rect = mTouchpadAreaRect;
            final float threshold = mPointerEdgeMoveThreshold;
            final float edgeMoveStep = (mUseFastEdgeMovement ? FAST_POINTER_EDGE_MOVE_STEP :
                POINTER_EDGE_MOVE_STEP);

            if (isTopEdge(rect, event, pointerIndex, threshold)) {
                mMoveY -= edgeMoveStep;
            } else if (isBottomEdge(rect, event, pointerIndex, threshold)) {
                mMoveY += edgeMoveStep;
            }

            if (isLeftEdge(rect, event, pointerIndex, threshold)) {
                mMoveX -= edgeMoveStep;
            } else if (isRightEdge(rect, event, pointerIndex, threshold)) {
                mMoveX += edgeMoveStep;
            }
        }

        /**
         * Checks if an imminent end of the touch action is expected.
         * This is useful to prevent a jumping pointer when the finger is removed from the screen
         * because some touch screens are getting inaccurate as soon as the touch pressure is
         * decreasing.
         */
        private boolean isTouchEndPredicted(MotionEvent event, int pointerIndex) {
            if (mIsTouchEndPredicted) {
                if ((event.getEventTime() - mTouchEndPredictTime) > MAX_TOUCH_END_PREDICT_TIME) {
                    if (V) Log.v(TAG, "invalid predicted touch end (Time)");
                    mIsTouchEndPredicted = false;
                }

                if (pointerIndex > -1) {
                    if (event.getPressure(pointerIndex) >= mPreviousPoint.pressure) {
                        if (V) Log.v(TAG, "invalid predicted touch end (Pressure)");
                        mIsTouchEndPredicted = false;
                    }

                    final float x = mTouchEndPredictPoint.x - event.getX(pointerIndex);
                    final float y = mTouchEndPredictPoint.y - event.getY(pointerIndex);
                    if (isDistanceReached(x, y, mMaxTouchEndPredictDistanceSquare)) {
                        if (V) Log.v(TAG, "invalid predicted touch end (Move)");
                        mIsTouchEndPredicted = false;
                    }
                }
            } else {
                if (pointerIndex > -1) {
                    final float x = event.getX(pointerIndex) - mPreviousPoint.x;
                    final float y = event.getY(pointerIndex) - mPreviousPoint.y;
                    if ((event.getPressure(pointerIndex) < mPreviousPoint.pressure) &&
                            !isDistanceReached(x, y, mMaxTouchEndPredictDistanceSquare)){
                        mIsTouchEndPredicted = true;
                        mTouchEndPredictTime = event.getEventTime();
                        event.getPointerCoords(pointerIndex, mTouchEndPredictPoint);

                        if (V) Log.v(TAG, "touch end predicted");
                    }
                }
            }

            return mIsTouchEndPredicted;
        }
    }


    /**
     * Touchpad event listener that scrolls the Mouse Wheel.
     */
    private class ScrollSubListener extends SubListener {

        /** The current scroll mode. */
        private int mScrollMode;

        /** Stores the next movement on the Y-axis. */
        private float mMoveY;

        /** Stores the next movement on the X-axis. */
        private float mMoveX;

        /** The current fling scroll movement on the Y-axis. */
        private float mFlingScrollMoveY;

        /** The current fling scroll movement on the X-axis. */
        private float mFlingScrollMoveX;

        /** The retained fling scroll movement on the Y-axis from the previous fling scroll. */
        private float mRetainedFlingScrollMoveY;

        /** The retained fling scroll movement on the X-axis from the previous fling scroll. */
        private float mRetainedFlingScrollMoveX;


        private final Runnable mFlingScrollRunnable = new Runnable()
        {
             @Override
             public void run() {
                 if (mTouchpadView.isShown() && isActive() &&
                         checkFlingScrollMoveThreshold(0)) {
                     mFlingScrollMoveY = subtractFlingScrollMoveFriction(mFlingScrollMoveY);
                     mFlingScrollMoveX = subtractFlingScrollMoveFriction(mFlingScrollMoveX);

                     scrollWheel(mFlingScrollMoveY, mFlingScrollMoveX);

                     mTouchpadView.postDelayed(mFlingScrollRunnable, FLING_SCROLL_LOOP_TIME);
                 } else {
                     changeSubListener(mIdleSubListener, null);
                 }
             }
        };


        @Override
        protected void resetMembers() {
            mScrollMode = TouchpadView.SCROLL_MODE_VERTICAL;
            mRetainedFlingScrollMoveY = 0.0f;
            mRetainedFlingScrollMoveX = 0.0f;
            resetMoveValues();
        }

        private void resetMoveValues() {
            mMoveY = 0.0f;
            mMoveX = 0.0f;
            mFlingScrollMoveY = 0.0f;
            mFlingScrollMoveX = 0.0f;
        }

        @Override
        protected void start() {
            super.start();

            mTouchpadView.performModeChangedFeedback();
            onScrollModeChanged(mScrollMode, TouchpadView.SCROLL_MODE_NONE);
        }

        @Override
        protected void stop() {
            super.stop();

            mTouchpadView.performModeChangedFeedback();
            onScrollModeChanged(TouchpadView.SCROLL_MODE_NONE, mScrollMode);
        }

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            stopFlingScroll();

            return super.onTouch(view, event);
        }

        @Override
        protected void onTouchPointerDown(View view, MotionEvent event) {
            if (mPointerIdList.size() == 1) {
                mRetainedFlingScrollMoveY = mFlingScrollMoveY;
                mRetainedFlingScrollMoveX = mFlingScrollMoveX;
                resetMoveValues();
            }
        }

        @Override
        protected void onTouchPointerUp(View view, MotionEvent event) {
            if (mPointerIdList.isEmpty()) {
                if (mFlingScroll && checkFlingScrollMoveThreshold(mFlingScrollThreshold)) {
                    startFlingScroll();
                } else {
                    changeSubListener(mIdleSubListener, event);
                }
            }
        }

        @Override
        protected void onTouchMove(View view, MotionEvent event) {
            int pointerIndex = getMainPointerIndex(event);
            if (pointerIndex < 0) {
                return;
            }

            final float deltaY = event.getY(pointerIndex) - mPreviousPoint.y;
            final float deltaYPerMs = getDeltaPerMs(event, deltaY);
            final float deltaX = event.getX(pointerIndex) - mPreviousPoint.x;
            final float deltaXPerMs = getDeltaPerMs(event, deltaX);

            reconsiderScrollMode(deltaYPerMs, deltaXPerMs);

            mFlingScrollMoveY = deltaYPerMs * FLING_SCROLL_LOOP_TIME;
            mFlingScrollMoveX = deltaXPerMs * FLING_SCROLL_LOOP_TIME;

            scrollWheel(deltaY, deltaX);
        }

        public int getScrollMode() {
            return mScrollMode;
        }
        public void setScrollMode(int value) {
            if (value != mScrollMode) {
                final int oldMode = mScrollMode;
                mScrollMode = value;
                onScrollModeChanged(mScrollMode, oldMode);
            }
        }

        private void reconsiderScrollMode(float deltaYPerMs, float deltaXPerMs) {
            switch (mScrollMode) {
            case TouchpadView.SCROLL_MODE_VERTICAL:
                if ((Math.abs(deltaXPerMs) > mChangeScrollModeThreshold) &&
                        (Math.abs(deltaXPerMs) > Math.abs(deltaYPerMs * 2))) {
                    setScrollMode(TouchpadView.SCROLL_MODE_ALL);
                }
                break;
            case TouchpadView.SCROLL_MODE_HORIZONTAL:
                if ((Math.abs(deltaYPerMs) > mChangeScrollModeThreshold) &&
                        (Math.abs(deltaYPerMs) > Math.abs(deltaXPerMs * 2))) {
                    setScrollMode(TouchpadView.SCROLL_MODE_ALL);
                }
                break;
            }
        }

        private boolean isVerticalScrollActive() {
            return ((mScrollMode == TouchpadView.SCROLL_MODE_VERTICAL) ||
                    (mScrollMode == TouchpadView.SCROLL_MODE_ALL));
        }

        private boolean isHorizontalScrollActive() {
            return ((mScrollMode == TouchpadView.SCROLL_MODE_HORIZONTAL) ||
                    (mScrollMode == TouchpadView.SCROLL_MODE_ALL));
        }

        /** Gets a movement delta per millisecond value. */
        private float getDeltaPerMs(MotionEvent event, float deltaValue) {
            if (mPreviousEventTime > 0) {
                final long timespan = event.getEventTime() - mPreviousEventTime;
                if (timespan > 0) {
                    return deltaValue / timespan;
                }
            }
            return 0.0f;
        }

        private float getSensitivity(boolean smooth) {
            if (smooth) {
                return mSmoothScrollSensitivity;
            } else {
                return mStepScrollSensitivity;
            }
        }

        /** Converts the Y-axis touch move value to the HID Report scroll value. */
        private int convertTouchDeltaValueY(float value, boolean smooth) {
            return -convertTouchDeltaValueX(value, smooth);
        }

        /** Converts the Y-axis HID Report scroll value to the touch move value. */
        private float convertReportDeltaValueY(int value, boolean smooth) {
            return -convertReportDeltaValueX(value, smooth);
        }

        /** Converts the X-axis touch move value to the HID Report scroll value. */
        private int convertTouchDeltaValueX(float value, boolean smooth) {
            final float sensitivity = getSensitivity(smooth);
            final int result = (int)(value / mDisplayDensity * sensitivity);
            return (mInvertScroll ? -result : result);
        }

        /** Converts the X-axis HID Report scroll value to the touch move value. */
        private float convertReportDeltaValueX(int value, boolean smooth) {
            final float sensitivity = getSensitivity(smooth);
            if (sensitivity != 0) {
                final float result = (value * mDisplayDensity / sensitivity);
                return (mInvertScroll ? -result : result);
            } else {
                return 0.0f;
            }
        }

        private void scrollWheel(float deltaY, float deltaX) {
            mMoveY = (isVerticalScrollActive() ? mMoveY + deltaY : 0.0f);
            mMoveX = (isHorizontalScrollActive() ? mMoveX + deltaX : 0.0f);

            if (mHidMouse != null) {
                final boolean smoothY = mHidMouse.isSmoothScrollYOn();
                final boolean smoothX = mHidMouse.isSmoothScrollXOn();
                final int reportMoveY = convertTouchDeltaValueY(mMoveY, smoothY);
                final int reportMoveX = convertTouchDeltaValueX(mMoveX, smoothX);
                if ((reportMoveY != 0) || (reportMoveX != 0)) {
                    mHidMouse.scrollWheel(reportMoveY, reportMoveX);

                    // Subtract only the actually moved value
                    mMoveY -= convertReportDeltaValueY(reportMoveY, smoothY);
                    mMoveX -= convertReportDeltaValueX(reportMoveX, smoothX);
                }
            }
        }

        /** Returns true if one of the fling scroll move values is bigger than the threshold. */
        private boolean checkFlingScrollMoveThreshold(float threshold) {
            return ((isVerticalScrollActive() && (Math.abs(mFlingScrollMoveY) > threshold)) ||
                    (isHorizontalScrollActive() && (Math.abs(mFlingScrollMoveX) > threshold)));
        }

        private void startFlingScroll() {
            if (checkFlingScrollMoveThreshold(mMultipleFlingScrollThreshold)) {
                if (((mFlingScrollMoveY > 0) && (mRetainedFlingScrollMoveY > 0)) ||
                        ((mFlingScrollMoveY < 0) && (mRetainedFlingScrollMoveY < 0))) {
                    mFlingScrollMoveY += mRetainedFlingScrollMoveY;
                }
                if (((mFlingScrollMoveX > 0) && (mRetainedFlingScrollMoveX > 0)) ||
                        ((mFlingScrollMoveX < 0) && (mRetainedFlingScrollMoveX < 0))) {
                    mFlingScrollMoveX += mRetainedFlingScrollMoveX;
                }
            }

            mTouchpadView.postDelayed(mFlingScrollRunnable, FLING_SCROLL_LOOP_TIME);
        }

        private void stopFlingScroll() {
            mTouchpadView.removeCallbacks(mFlingScrollRunnable);
        }

        private float subtractFlingScrollMoveFriction(float moveValue) {
            if (Math.abs(moveValue) <= mFlingScrollMoveFriction) {
                return 0.0f;
            } else if (moveValue < 0) {
                return (moveValue + mFlingScrollMoveFriction);
            } else {
                return (moveValue - mFlingScrollMoveFriction);
            }
        }
    }


    /**
     * Touchpad event listener for a pinch-to-zoom gesture.
     */
    private class PinchZoomSubListener extends SubListener {

        /** The previous span of the touch points. */
        private float mPreviousSpan;

        /** Stores the unprocessed span delta. */
        private float mSpanDelta;


        @Override
        protected void resetMembers() {
            mPreviousSpan = -1f;
            mSpanDelta = 0f;
        }

        @Override
        protected void start() {
            super.start();

            mTouchpadView.performModeChangedFeedback();
            if (mHidKeyboard != null) {
                mHidKeyboard.pressModifierKey(HidKeyboard.MODIFIER_LEFT_CTRL);
            }
        }

        @Override
        protected void stop() {
            super.stop();

            mTouchpadView.performModeChangedFeedback();
            if (mHidKeyboard != null) {
                mHidKeyboard.releaseModifierKey(HidKeyboard.MODIFIER_LEFT_CTRL);
            }
        }

        @Override
        protected void onTouchPointerDown(View view, MotionEvent event) {
        }

        @Override
        protected void onTouchPointerUp(View view, MotionEvent event) {
            if (mPointerIdList.isEmpty()) {
                changeSubListener(mIdleSubListener, event);
            }
        }

        @Override
        protected void onTouchMove(View view, MotionEvent event) {
            if (mPointerIdList.size() != 2) {
                resetMembers();
                return;
            }

            final int firstPointerIndex = getMainPointerIndex(event);
            final int secondPointerIndex = event.findPointerIndex(mPointerIdList.getValue(1));
            if ((firstPointerIndex < 0) || (secondPointerIndex < 0)) {
                return;
            }

            final float currentSpan = getSpan(event.getX(firstPointerIndex),
                    event.getY(firstPointerIndex),
                    event.getX(secondPointerIndex),
                    event.getY(secondPointerIndex));

            if (mPreviousSpan < 0) {
                mPreviousSpan = currentSpan;
                return;
            }

            mSpanDelta += currentSpan - mPreviousSpan;

            if (mHidMouse != null) {
                int reportScroll = convertTouchDeltaValue(mSpanDelta);
                if ((reportScroll != 0)) {
                    if (mHidMouse.isSmoothScrollYOn()) {
                        /*
                         * Report only complete scroll steps and ignore the intermediate steps of
                         * the Smooth Scroll feature. Otherwise most programs would zoom too fast.
                         */
                        mHidMouse.scrollWheel(reportScroll * SMOOTH_SCROLL_STEPS, 0);
                    } else {
                        mHidMouse.scrollWheel(reportScroll, 0);
                    }

                    // Subtract only the actually reported value
                    mSpanDelta -= convertReportDeltaValue(reportScroll);
                }
            }

            mPreviousSpan = currentSpan;
        }

        /** Converts the touch span value to a HID Report scroll value. */
        private int convertTouchDeltaValue(float value) {
            return (int)(value / mDisplayDensity * mPinchZoomSensitivity / SMOOTH_SCROLL_STEPS);
        }

        /** Converts the HID Report scroll value to a touch span value. */
        private float convertReportDeltaValue(int value) {
            if (mPinchZoomSensitivity != 0) {
                return (value * mDisplayDensity / mPinchZoomSensitivity * SMOOTH_SCROLL_STEPS);
            } else {
                return 0.0f;
            }
        }
    }
}




Java Source Code List

org.ronsdev.bluectrl.ChangelogDialog.java
org.ronsdev.bluectrl.CharKeyReportMap.java
org.ronsdev.bluectrl.DeviceManager.java
org.ronsdev.bluectrl.DevicePreferenceActivity.java
org.ronsdev.bluectrl.DeviceSettings.java
org.ronsdev.bluectrl.HidKeyboard.java
org.ronsdev.bluectrl.HidMouse.java
org.ronsdev.bluectrl.IntArrayList.java
org.ronsdev.bluectrl.KeyEventFuture.java
org.ronsdev.bluectrl.MainActivity.java
org.ronsdev.bluectrl.OnMouseButtonClickListener.java
org.ronsdev.bluectrl.PairedDevice.java
org.ronsdev.bluectrl.PairingActivity.java
org.ronsdev.bluectrl.TouchpadActivity.java
org.ronsdev.bluectrl.TouchpadTutorialActivity.java
org.ronsdev.bluectrl.daemon.DaemonActivity.java
org.ronsdev.bluectrl.daemon.DaemonCallbackReceiver.java
org.ronsdev.bluectrl.daemon.DaemonListActivity.java
org.ronsdev.bluectrl.daemon.DaemonService.java
org.ronsdev.bluectrl.widget.ComposeTextLayout.java
org.ronsdev.bluectrl.widget.FloatSliderPreference.java
org.ronsdev.bluectrl.widget.KeyboardInputView.java
org.ronsdev.bluectrl.widget.MouseTouchListener.java
org.ronsdev.bluectrl.widget.OnKeyboardComposingTextListener.java
org.ronsdev.bluectrl.widget.OnScrollModeChangedListener.java
org.ronsdev.bluectrl.widget.OnSendComposeTextListener.java
org.ronsdev.bluectrl.widget.OnTouchpadGestureListener.java
org.ronsdev.bluectrl.widget.SummaryListPreference.java
org.ronsdev.bluectrl.widget.TouchpadViewGestureListener.java
org.ronsdev.bluectrl.widget.TouchpadView.java