Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6c3304ea authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Support bimanual gesture input

Change-Id: Id86dc93fb7115ab4a6d05b3c05652df35ad08c12
parent 9fa0736d
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -992,7 +992,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
    }

    @Override
    public void showGestureTrail(PointerTracker tracker) {
    public void showGesturePreviewTrail(PointerTracker tracker) {
        locatePreviewPlacerView();
        mPreviewPlacerView.invalidatePointer(tracker);
    }
+121 −132
Original line number Diff line number Diff line
@@ -80,7 +80,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        public void invalidateKey(Key key);
        public void showKeyPreview(PointerTracker tracker);
        public void dismissKeyPreview(PointerTracker tracker);
        public void showGestureTrail(PointerTracker tracker);
        public void showGesturePreviewTrail(PointerTracker tracker);
    }

    public interface TimerProxy {
@@ -152,8 +152,6 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    private static boolean sNeedsPhantomSuddenMoveEventHack;

    private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
    private static final InputPointers sAggregratedPointers = new InputPointers(
            GestureStroke.DEFAULT_CAPACITY);
    private static PointerTrackerQueue sPointerTrackerQueue;

    public final int mPointerId;
@@ -166,13 +164,13 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    private Keyboard mKeyboard;
    private int mKeyQuarterWidthSquared;

    private boolean mIsAlphabetKeyboard;
    private boolean mIsPossibleGesture = false;
    private boolean mInGesture = false;

    // TODO: Remove these variables
    private int mLastRecognitionPointSize = 0;
    private long mLastRecognitionTime = 0;
    private boolean mIsDetectingGesture = false; // per PointerTracker.
    private static boolean sInGesture = false;
    private static long sGestureFirstDownTime;
    private static final InputPointers sAggregratedPointers = new InputPointers(
            GestureStroke.DEFAULT_CAPACITY);
    private static int sLastRecognitionPointSize = 0;
    private static long sLastRecognitionTime = 0;

    // The position and time at which first down event occurred.
    private long mDownTime;
@@ -290,40 +288,6 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        }
    }

    // TODO: To handle multi-touch gestures we may want to move this method to
    // {@link PointerTrackerQueue}.
    private static InputPointers getIncrementalBatchPoints() {
        final int trackersSize = sTrackers.size();
        for (int i = 0; i < trackersSize; ++i) {
            final PointerTracker tracker = sTrackers.get(i);
            tracker.mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(
                    sAggregratedPointers);
        }
        return sAggregratedPointers;
    }

    // TODO: To handle multi-touch gestures we may want to move this method to
    // {@link PointerTrackerQueue}.
    private static InputPointers getAllBatchPoints() {
        final int trackersSize = sTrackers.size();
        for (int i = 0; i < trackersSize; ++i) {
            final PointerTracker tracker = sTrackers.get(i);
            tracker.mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
        }
        return sAggregratedPointers;
    }

    // TODO: To handle multi-touch gestures we may want to move this method to
    // {@link PointerTrackerQueue}.
    public static void clearBatchInputPointsOfAllPointerTrackers() {
        final int trackersSize = sTrackers.size();
        for (int i = 0; i < trackersSize; ++i) {
            final PointerTracker tracker = sTrackers.get(i);
            tracker.mGestureStrokeWithPreviewTrail.reset();
        }
        sAggregratedPointers.reset();
    }

    private PointerTracker(final int id, final KeyEventHandler handler) {
        if (handler == null) {
            throw new NullPointerException();
@@ -338,7 +302,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {

    // Returns true if keyboard has been changed by this callback.
    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
        if (mInGesture) {
        if (sInGesture) {
            return false;
        }
        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -394,7 +358,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    // primaryCode is different from {@link Key#mCode}.
    private void callListenerOnRelease(final Key key, final int primaryCode,
            final boolean withSliding) {
        if (mInGesture) {
        if (sInGesture) {
            return;
        }
        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -428,7 +392,6 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    private void setKeyDetectorInner(final KeyDetector keyDetector) {
        mKeyDetector = keyDetector;
        mKeyboard = keyDetector.getKeyboard();
        mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
        mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
        final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
        if (newKey != mCurrentKey) {
@@ -502,7 +465,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
            return;
        }

        if (!key.noKeyPreview() && !mInGesture) {
        if (!key.noKeyPreview() && !sInGesture) {
            mDrawingProxy.showKeyPreview(this);
        }
        updatePressKeyGraphics(key);
@@ -577,50 +540,65 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        return newKey;
    }

    private static int getActivePointerTrackerCount() {
        return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
    }

    private void startBatchInput() {
        if (DEBUG_LISTENER) {
            Log.d(TAG, "onStartBatchInput");
        }
        mInGesture = true;
        sInGesture = true;
        mListener.onStartBatchInput();
        mDrawingProxy.showGesturePreviewTrail(this);
    }

    private void updateBatchInput(final InputPointers batchPoints) {
    private void updateBatchInput(final long eventTime) {
        synchronized (sAggregratedPointers) {
            mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
            final int size = sAggregratedPointers.getPointerSize();
            if (size > sLastRecognitionPointSize
                    && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
                sLastRecognitionPointSize = size;
                sLastRecognitionTime = eventTime;
                if (DEBUG_LISTENER) {
            Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
                    Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
                }
                mListener.onUpdateBatchInput(sAggregratedPointers);
            }
        }
        mListener.onUpdateBatchInput(batchPoints);
        mDrawingProxy.showGesturePreviewTrail(this);
    }

    private void endBatchInput(final InputPointers batchPoints) {
    private void endBatchInput() {
        synchronized (sAggregratedPointers) {
            mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
            if (getActivePointerTrackerCount() == 1) {
                if (DEBUG_LISTENER) {
            Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
                    Log.d(TAG, "onEndBatchInput: batchPoints="
                            + sAggregratedPointers.getPointerSize());
                }
        mListener.onEndBatchInput(batchPoints);
        clearBatchInputRecognitionStateOfThisPointerTracker();
                sInGesture = false;
                mListener.onEndBatchInput(sAggregratedPointers);
                clearBatchInputPointsOfAllPointerTrackers();
            }

    private void abortBatchInput() {
        clearBatchInputRecognitionStateOfThisPointerTracker();
        clearBatchInputPointsOfAllPointerTrackers();
        }
        mDrawingProxy.showGesturePreviewTrail(this);
    }

    private void clearBatchInputRecognitionStateOfThisPointerTracker() {
        mIsPossibleGesture = false;
        mInGesture = false;
        mLastRecognitionPointSize = 0;
        mLastRecognitionTime = 0;
    private static void abortBatchInput() {
        clearBatchInputPointsOfAllPointerTrackers();
    }

    private boolean updateBatchInputRecognitionState(final long eventTime, final int size) {
        if (size > mLastRecognitionPointSize
                && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
            mLastRecognitionPointSize = size;
            mLastRecognitionTime = eventTime;
            return true;
    private static void clearBatchInputPointsOfAllPointerTrackers() {
        final int trackersSize = sTrackers.size();
        for (int i = 0; i < trackersSize; ++i) {
            final PointerTracker tracker = sTrackers.get(i);
            tracker.mGestureStrokeWithPreviewTrail.reset();
        }
        return false;
        sAggregratedPointers.reset();
        sLastRecognitionPointSize = 0;
        sLastRecognitionTime = 0;
    }

    public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
@@ -671,8 +649,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
            }
        }

        final PointerTrackerQueue queue = sPointerTrackerQueue;
        final Key key = getKeyOn(x, y);
        final PointerTrackerQueue queue = sPointerTrackerQueue;
        if (queue != null) {
            if (key != null && key.isModifier()) {
                // Before processing a down event of modifier key, all pointers already being
@@ -682,16 +660,26 @@ public class PointerTracker implements PointerTrackerQueue.Element {
            queue.add(this);
        }
        onDownEventInternal(x, y, eventTime);
        if (queue != null && queue.size() == 1) {
            mIsPossibleGesture = false;
        if (!sShouldHandleGesture) {
            return;
        }
        final int activePointerTrackerCount = getActivePointerTrackerCount();
        if (activePointerTrackerCount == 1) {
            mIsDetectingGesture = false;
            // A gesture should start only from the letter key.
            if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel
                    && key != null && Keyboard.isLetterCode(key.mCode)) {
                mIsPossibleGesture = true;
                // TODO: pointer times should be relative to first down even in entire batch input
                // instead of resetting to 0 for each new down event.
                mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false);
            final boolean isAlphabetKeyboard = (mKeyboard != null)
                    && mKeyboard.mId.isAlphabetKeyboard();
            if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
                    && Keyboard.isLetterCode(key.mCode)) {
                mIsDetectingGesture = true;
                sGestureFirstDownTime = eventTime;
                mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
            }
        } else if (sInGesture && activePointerTrackerCount > 1) {
            mIsDetectingGesture = true;
            final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
            mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
                    false /* isHistorical */);
        }
    }

@@ -727,22 +715,18 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        mIsInSlidingKeyInput = true;
    }

    private void onGestureMoveEvent(final PointerTracker tracker, final int x, final int y,
            final long eventTime, final boolean isHistorical, final Key key) {
        final int gestureTime = (int)(eventTime - tracker.getDownTime());
        if (sShouldHandleGesture && mIsPossibleGesture) {
    private void onGestureMoveEvent(final int x, final int y, final long eventTime,
            final boolean isHistorical, final Key key) {
        final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
        if (mIsDetectingGesture) {
            final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
            stroke.addPoint(x, y, gestureTime, isHistorical);
            if (!mInGesture && stroke.isStartOfAGesture()) {
            if (!sInGesture && stroke.isStartOfAGesture()) {
                startBatchInput();
            }
        }

        if (key != null && mInGesture) {
            final InputPointers batchPoints = getIncrementalBatchPoints();
            mDrawingProxy.showGestureTrail(this);
            if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
                updateBatchInput(batchPoints);
            if (sInGesture && key != null) {
                updateBatchInput(eventTime);
            }
        }
    }
@@ -755,7 +739,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
            return;
        }

        if (me != null) {
        if (sShouldHandleGesture && me != null) {
            // Add historical points to gesture path.
            final int pointerIndex = me.findPointerIndex(mPointerId);
            final int historicalSize = me.getHistorySize();
@@ -763,24 +747,31 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
                final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
                final long historicalTime = me.getHistoricalEventTime(h);
                onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
                onGestureMoveEvent(historicalX, historicalY, historicalTime,
                        true /* isHistorical */, null);
            }
        }

        onMoveEventInternal(x, y, eventTime);
    }

    private void onMoveEventInternal(final int x, final int y, final long eventTime) {
        final int lastX = mLastX;
        final int lastY = mLastY;
        final Key oldKey = mCurrentKey;
        Key key = onMoveKey(x, y);

        if (sShouldHandleGesture) {
            // Register move event on gesture tracker.
        onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
        if (mInGesture) {
            onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
            if (sInGesture) {
                mIgnoreModifierKey = true;
                mTimerProxy.cancelLongPressTimer();
                mIsInSlidingKeyInput = true;
                mCurrentKey = null;
                setReleasedKeyGraphics(oldKey);
                return;
            }
        }

        if (key != null) {
@@ -825,7 +816,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                    // TODO: Should find a way to balance gesture detection and this hack.
                    if (sNeedsPhantomSuddenMoveEventHack
                            && lastMoveSquared >= mKeyQuarterWidthSquared
                            && !mIsPossibleGesture) {
                            && !mIsDetectingGesture) {
                        if (DEBUG_MODE) {
                            Log.w(TAG, String.format("onMoveEvent:"
                                    + " phantom sudden move event is translated to "
@@ -843,12 +834,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                        // touch panels when there are close multiple touches.
                        // Caveat: When in chording input mode with a modifier key, we don't use
                        // this hack.
                        final PointerTrackerQueue queue = sPointerTrackerQueue;
                        if (queue != null && queue.size() > 1
                                && !queue.hasModifierKeyOlderThan(this)) {
                        if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
                                && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
                            onUpEventInternal();
                        }
                        if (!mIsPossibleGesture) {
                        if (!mIsDetectingGesture) {
                            mKeyAlreadyProcessed = true;
                        }
                        setReleasedKeyGraphics(oldKey);
@@ -866,7 +856,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                if (mIsAllowedSlidingKeyInput) {
                    onMoveToNewKey(key, x, y);
                } else {
                    if (!mIsPossibleGesture) {
                    if (!mIsDetectingGesture) {
                        mKeyAlreadyProcessed = true;
                    }
                }
@@ -881,7 +871,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {

        final PointerTrackerQueue queue = sPointerTrackerQueue;
        if (queue != null) {
            if (!mInGesture) {
            if (!sInGesture) {
                if (mCurrentKey != null && mCurrentKey.isModifier()) {
                    // Before processing an up event of modifier key, all pointers already being
                    // tracked should be released.
@@ -890,9 +880,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                    queue.releaseAllPointersOlderThan(this, eventTime);
                }
            }
            queue.remove(this);
        }
        onUpEventInternal();
        if (queue != null) {
            queue.remove(this);
        }
    }

    // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
@@ -910,34 +902,31 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    private void onUpEventInternal() {
        mTimerProxy.cancelKeyTimers();
        mIsInSlidingKeyInput = false;
        mIsPossibleGesture = false;
        mIsDetectingGesture = false;
        final Key currentKey = mCurrentKey;
        mCurrentKey = null;
        // Release the last pressed key.
        setReleasedKeyGraphics(mCurrentKey);
        setReleasedKeyGraphics(currentKey);
        if (mIsShowingMoreKeysPanel) {
            mDrawingProxy.dismissMoreKeysPanel();
            mIsShowingMoreKeysPanel = false;
        }

        if (mInGesture) {
            // Register up event on gesture tracker.
            // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
            // and/or tapping mode?
            endBatchInput(getAllBatchPoints());
            if (mCurrentKey != null) {
                callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
                mCurrentKey = null;
        if (sInGesture) {
            if (currentKey != null) {
                callListenerOnRelease(currentKey, currentKey.mCode, true);
            }
            mDrawingProxy.showGestureTrail(this);
            endBatchInput();
            return;
        }
        // This event will be recognized as a regular code input. Clear unused batch points so they
        // are not mistakenly included in the next batch event.
        // This event will be recognized as a regular code input. Clear unused possible batch points
        // so they are not mistakenly displayed as preview.
        clearBatchInputPointsOfAllPointerTrackers();
        if (mKeyAlreadyProcessed) {
            return;
        }
        if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
            detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
        if (currentKey != null && !currentKey.isRepeatable()) {
            detectAndSendKey(currentKey, mKeyX, mKeyY);
        }
    }

@@ -981,7 +970,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    }

    private void startRepeatKey(final Key key) {
        if (key != null && key.isRepeatable() && !mInGesture) {
        if (key != null && key.isRepeatable() && !sInGesture) {
            onRegisterKey(key);
            mTimerProxy.startKeyRepeatTimer(this);
        }
@@ -1010,7 +999,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    }

    private void startLongPressTimer(final Key key) {
        if (key != null && key.isLongPressEnabled() && !mInGesture) {
        if (key != null && key.isLongPressEnabled() && !sInGesture) {
            mTimerProxy.startLongPressTimer(this);
        }
    }
+5 −1
Original line number Diff line number Diff line
@@ -130,8 +130,12 @@ public class GestureStroke {
    }

    private void appendBatchPoints(final InputPointers out, final int size) {
        final int length = size - mLastIncrementalBatchSize;
        if (length <= 0) {
            return;
        }
        out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
                mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
                mLastIncrementalBatchSize, length);
        mLastIncrementalBatchSize = size;
    }