am 263caae3: Merge "Support bimanual gesture input" into jb-mr1-dev

* commit '263caae3c91314883b37c2db314342fab5c7903f':
  Support bimanual gesture input
This commit is contained in:
Tadashi G. Takaoka 2012-08-28 02:31:56 -07:00 committed by Android Git Automerger
commit 5d309b35d2
3 changed files with 127 additions and 134 deletions

View file

@ -992,7 +992,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
} }
@Override @Override
public void showGestureTrail(PointerTracker tracker) { public void showGesturePreviewTrail(PointerTracker tracker) {
locatePreviewPlacerView(); locatePreviewPlacerView();
mPreviewPlacerView.invalidatePointer(tracker); mPreviewPlacerView.invalidatePointer(tracker);
} }

View file

@ -80,7 +80,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
public void invalidateKey(Key key); public void invalidateKey(Key key);
public void showKeyPreview(PointerTracker tracker); public void showKeyPreview(PointerTracker tracker);
public void dismissKeyPreview(PointerTracker tracker); public void dismissKeyPreview(PointerTracker tracker);
public void showGestureTrail(PointerTracker tracker); public void showGesturePreviewTrail(PointerTracker tracker);
} }
public interface TimerProxy { public interface TimerProxy {
@ -152,8 +152,6 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private static boolean sNeedsPhantomSuddenMoveEventHack; private static boolean sNeedsPhantomSuddenMoveEventHack;
private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList(); private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static final InputPointers sAggregratedPointers = new InputPointers(
GestureStroke.DEFAULT_CAPACITY);
private static PointerTrackerQueue sPointerTrackerQueue; private static PointerTrackerQueue sPointerTrackerQueue;
public final int mPointerId; public final int mPointerId;
@ -166,13 +164,13 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private Keyboard mKeyboard; private Keyboard mKeyboard;
private int mKeyQuarterWidthSquared; private int mKeyQuarterWidthSquared;
private boolean mIsAlphabetKeyboard; private boolean mIsDetectingGesture = false; // per PointerTracker.
private boolean mIsPossibleGesture = false; private static boolean sInGesture = false;
private boolean mInGesture = false; private static long sGestureFirstDownTime;
private static final InputPointers sAggregratedPointers = new InputPointers(
// TODO: Remove these variables GestureStroke.DEFAULT_CAPACITY);
private int mLastRecognitionPointSize = 0; private static int sLastRecognitionPointSize = 0;
private long mLastRecognitionTime = 0; private static long sLastRecognitionTime = 0;
// The position and time at which first down event occurred. // The position and time at which first down event occurred.
private long mDownTime; 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) { private PointerTracker(final int id, final KeyEventHandler handler) {
if (handler == null) { if (handler == null) {
throw new NullPointerException(); throw new NullPointerException();
@ -338,7 +302,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// Returns true if keyboard has been changed by this callback. // Returns true if keyboard has been changed by this callback.
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) { private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
if (mInGesture) { if (sInGesture) {
return false; return false;
} }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@ -394,7 +358,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// primaryCode is different from {@link Key#mCode}. // primaryCode is different from {@link Key#mCode}.
private void callListenerOnRelease(final Key key, final int primaryCode, private void callListenerOnRelease(final Key key, final int primaryCode,
final boolean withSliding) { final boolean withSliding) {
if (mInGesture) { if (sInGesture) {
return; return;
} }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@ -428,7 +392,6 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private void setKeyDetectorInner(final KeyDetector keyDetector) { private void setKeyDetectorInner(final KeyDetector keyDetector) {
mKeyDetector = keyDetector; mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard(); mKeyboard = keyDetector.getKeyboard();
mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth); mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) { if (newKey != mCurrentKey) {
@ -502,7 +465,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return; return;
} }
if (!key.noKeyPreview() && !mInGesture) { if (!key.noKeyPreview() && !sInGesture) {
mDrawingProxy.showKeyPreview(this); mDrawingProxy.showKeyPreview(this);
} }
updatePressKeyGraphics(key); updatePressKeyGraphics(key);
@ -577,50 +540,65 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return newKey; return newKey;
} }
private static int getActivePointerTrackerCount() {
return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
}
private void startBatchInput() { private void startBatchInput() {
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput"); Log.d(TAG, "onStartBatchInput");
} }
mInGesture = true; sInGesture = true;
mListener.onStartBatchInput(); 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) { if (DEBUG_LISTENER) {
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
} }
mListener.onUpdateBatchInput(batchPoints); mListener.onUpdateBatchInput(sAggregratedPointers);
}
}
mDrawingProxy.showGesturePreviewTrail(this);
} }
private void endBatchInput(final InputPointers batchPoints) { private void endBatchInput() {
synchronized (sAggregratedPointers) {
mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
if (getActivePointerTrackerCount() == 1) {
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize()); Log.d(TAG, "onEndBatchInput: batchPoints="
+ sAggregratedPointers.getPointerSize());
} }
mListener.onEndBatchInput(batchPoints); sInGesture = false;
clearBatchInputRecognitionStateOfThisPointerTracker(); mListener.onEndBatchInput(sAggregratedPointers);
clearBatchInputPointsOfAllPointerTrackers();
}
}
mDrawingProxy.showGesturePreviewTrail(this);
}
private static void abortBatchInput() {
clearBatchInputPointsOfAllPointerTrackers(); clearBatchInputPointsOfAllPointerTrackers();
} }
private void abortBatchInput() { private static void clearBatchInputPointsOfAllPointerTrackers() {
clearBatchInputRecognitionStateOfThisPointerTracker(); final int trackersSize = sTrackers.size();
clearBatchInputPointsOfAllPointerTrackers(); for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
tracker.mGestureStrokeWithPreviewTrail.reset();
} }
sAggregratedPointers.reset();
private void clearBatchInputRecognitionStateOfThisPointerTracker() { sLastRecognitionPointSize = 0;
mIsPossibleGesture = false; sLastRecognitionTime = 0;
mInGesture = false;
mLastRecognitionPointSize = 0;
mLastRecognitionTime = 0;
}
private boolean updateBatchInputRecognitionState(final long eventTime, final int size) {
if (size > mLastRecognitionPointSize
&& eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
mLastRecognitionPointSize = size;
mLastRecognitionTime = eventTime;
return true;
}
return false;
} }
public void processMotionEvent(final int action, final int x, final int y, final long eventTime, 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 Key key = getKeyOn(x, y);
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) { if (queue != null) {
if (key != null && key.isModifier()) { if (key != null && key.isModifier()) {
// Before processing a down event of modifier key, all pointers already being // 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); queue.add(this);
} }
onDownEventInternal(x, y, eventTime); onDownEventInternal(x, y, eventTime);
if (queue != null && queue.size() == 1) { if (!sShouldHandleGesture) {
mIsPossibleGesture = false; return;
// 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 int activePointerTrackerCount = getActivePointerTrackerCount();
if (activePointerTrackerCount == 1) {
mIsDetectingGesture = false;
// A gesture should start only from the letter key.
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; mIsInSlidingKeyInput = true;
} }
private void onGestureMoveEvent(final PointerTracker tracker, final int x, final int y, private void onGestureMoveEvent(final int x, final int y, final long eventTime,
final long eventTime, final boolean isHistorical, final Key key) { final boolean isHistorical, final Key key) {
final int gestureTime = (int)(eventTime - tracker.getDownTime()); final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
if (sShouldHandleGesture && mIsPossibleGesture) { if (mIsDetectingGesture) {
final GestureStroke stroke = mGestureStrokeWithPreviewTrail; final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
stroke.addPoint(x, y, gestureTime, isHistorical); stroke.addPoint(x, y, gestureTime, isHistorical);
if (!mInGesture && stroke.isStartOfAGesture()) { if (!sInGesture && stroke.isStartOfAGesture()) {
startBatchInput(); startBatchInput();
} }
}
if (key != null && mInGesture) { if (sInGesture && key != null) {
final InputPointers batchPoints = getIncrementalBatchPoints(); updateBatchInput(eventTime);
mDrawingProxy.showGestureTrail(this);
if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
updateBatchInput(batchPoints);
} }
} }
} }
@ -755,7 +739,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return; return;
} }
if (me != null) { if (sShouldHandleGesture && me != null) {
// Add historical points to gesture path. // Add historical points to gesture path.
final int pointerIndex = me.findPointerIndex(mPointerId); final int pointerIndex = me.findPointerIndex(mPointerId);
final int historicalSize = me.getHistorySize(); 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 historicalX = (int)me.getHistoricalX(pointerIndex, h);
final int historicalY = (int)me.getHistoricalY(pointerIndex, h); final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
final long historicalTime = me.getHistoricalEventTime(h); final long historicalTime = me.getHistoricalEventTime(h);
onGestureMoveEvent(this, historicalX, historicalY, historicalTime, onGestureMoveEvent(historicalX, historicalY, historicalTime,
true /* isHistorical */, null); 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 lastX = mLastX;
final int lastY = mLastY; final int lastY = mLastY;
final Key oldKey = mCurrentKey; final Key oldKey = mCurrentKey;
Key key = onMoveKey(x, y); Key key = onMoveKey(x, y);
if (sShouldHandleGesture) {
// Register move event on gesture tracker. // Register move event on gesture tracker.
onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key); onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
if (mInGesture) { if (sInGesture) {
mIgnoreModifierKey = true; mIgnoreModifierKey = true;
mTimerProxy.cancelLongPressTimer(); mTimerProxy.cancelLongPressTimer();
mIsInSlidingKeyInput = true; mIsInSlidingKeyInput = true;
mCurrentKey = null; mCurrentKey = null;
setReleasedKeyGraphics(oldKey); setReleasedKeyGraphics(oldKey);
return;
}
} }
if (key != null) { 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. // TODO: Should find a way to balance gesture detection and this hack.
if (sNeedsPhantomSuddenMoveEventHack if (sNeedsPhantomSuddenMoveEventHack
&& lastMoveSquared >= mKeyQuarterWidthSquared && lastMoveSquared >= mKeyQuarterWidthSquared
&& !mIsPossibleGesture) { && !mIsDetectingGesture) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
Log.w(TAG, String.format("onMoveEvent:" Log.w(TAG, String.format("onMoveEvent:"
+ " phantom sudden move event is translated to " + " 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. // touch panels when there are close multiple touches.
// Caveat: When in chording input mode with a modifier key, we don't use // Caveat: When in chording input mode with a modifier key, we don't use
// this hack. // this hack.
final PointerTrackerQueue queue = sPointerTrackerQueue; if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
if (queue != null && queue.size() > 1 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
&& !queue.hasModifierKeyOlderThan(this)) {
onUpEventInternal(); onUpEventInternal();
} }
if (!mIsPossibleGesture) { if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
} }
setReleasedKeyGraphics(oldKey); setReleasedKeyGraphics(oldKey);
@ -866,7 +856,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (mIsAllowedSlidingKeyInput) { if (mIsAllowedSlidingKeyInput) {
onMoveToNewKey(key, x, y); onMoveToNewKey(key, x, y);
} else { } else {
if (!mIsPossibleGesture) { if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
} }
} }
@ -881,7 +871,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final PointerTrackerQueue queue = sPointerTrackerQueue; final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) { if (queue != null) {
if (!mInGesture) { if (!sInGesture) {
if (mCurrentKey != null && mCurrentKey.isModifier()) { if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being // Before processing an up event of modifier key, all pointers already being
// tracked should be released. // tracked should be released.
@ -890,9 +880,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
queue.releaseAllPointersOlderThan(this, eventTime); queue.releaseAllPointersOlderThan(this, eventTime);
} }
} }
queue.remove(this);
} }
onUpEventInternal(); onUpEventInternal();
if (queue != null) {
queue.remove(this);
}
} }
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. // 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() { private void onUpEventInternal() {
mTimerProxy.cancelKeyTimers(); mTimerProxy.cancelKeyTimers();
mIsInSlidingKeyInput = false; mIsInSlidingKeyInput = false;
mIsPossibleGesture = false; mIsDetectingGesture = false;
final Key currentKey = mCurrentKey;
mCurrentKey = null;
// Release the last pressed key. // Release the last pressed key.
setReleasedKeyGraphics(mCurrentKey); setReleasedKeyGraphics(currentKey);
if (mIsShowingMoreKeysPanel) { if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel(); mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false; mIsShowingMoreKeysPanel = false;
} }
if (mInGesture) { if (sInGesture) {
// Register up event on gesture tracker. if (currentKey != null) {
// TODO: Figure out how to deal with multiple fingers that are in gesture, sliding, callListenerOnRelease(currentKey, currentKey.mCode, true);
// and/or tapping mode?
endBatchInput(getAllBatchPoints());
if (mCurrentKey != null) {
callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
mCurrentKey = null;
} }
mDrawingProxy.showGestureTrail(this); endBatchInput();
return; return;
} }
// This event will be recognized as a regular code input. Clear unused batch points so they // This event will be recognized as a regular code input. Clear unused possible batch points
// are not mistakenly included in the next batch event. // so they are not mistakenly displayed as preview.
clearBatchInputPointsOfAllPointerTrackers(); clearBatchInputPointsOfAllPointerTrackers();
if (mKeyAlreadyProcessed) { if (mKeyAlreadyProcessed) {
return; return;
} }
if (mCurrentKey != null && !mCurrentKey.isRepeatable()) { if (currentKey != null && !currentKey.isRepeatable()) {
detectAndSendKey(mCurrentKey, mKeyX, mKeyY); detectAndSendKey(currentKey, mKeyX, mKeyY);
} }
} }
@ -981,7 +970,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
} }
private void startRepeatKey(final Key key) { private void startRepeatKey(final Key key) {
if (key != null && key.isRepeatable() && !mInGesture) { if (key != null && key.isRepeatable() && !sInGesture) {
onRegisterKey(key); onRegisterKey(key);
mTimerProxy.startKeyRepeatTimer(this); mTimerProxy.startKeyRepeatTimer(this);
} }
@ -1010,7 +999,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
} }
private void startLongPressTimer(final Key key) { private void startLongPressTimer(final Key key) {
if (key != null && key.isLongPressEnabled() && !mInGesture) { if (key != null && key.isLongPressEnabled() && !sInGesture) {
mTimerProxy.startLongPressTimer(this); mTimerProxy.startLongPressTimer(this);
} }
} }

View file

@ -130,8 +130,12 @@ public class GestureStroke {
} }
private void appendBatchPoints(final InputPointers out, final int size) { private void appendBatchPoints(final InputPointers out, final int size) {
final int length = size - mLastIncrementalBatchSize;
if (length <= 0) {
return;
}
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates, out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
mLastIncrementalBatchSize, size - mLastIncrementalBatchSize); mLastIncrementalBatchSize, length);
mLastIncrementalBatchSize = size; mLastIncrementalBatchSize = size;
} }