Debounce touch navigation taps and button presses
Bug: 8990644
Change-Id: Ib4ef2e2ab699a109c12614c1d64e4b7e63b514b0
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 73fad08..5a5fc10 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1837,6 +1837,19 @@
}
}
+ /** Whether key will, by default, trigger a click on the focused view.
+ * @hide
+ */
+ public static final boolean isConfirmKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/** {@inheritDoc} */
@Override
public final int getDeviceId() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 95a2469..188ddf2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7945,21 +7945,17 @@
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean result = false;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER: {
- if ((mViewFlags & ENABLED_MASK) == DISABLED) {
- return true;
- }
- // Long clickable items don't necessarily have to be clickable
- if (((mViewFlags & CLICKABLE) == CLICKABLE ||
- (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
- (event.getRepeatCount() == 0)) {
- setPressed(true);
- checkForLongClick(0);
- return true;
- }
- break;
+ if (KeyEvent.isConfirmKey(event.getKeyCode())) {
+ if ((mViewFlags & ENABLED_MASK) == DISABLED) {
+ return true;
+ }
+ // Long clickable items don't necessarily have to be clickable
+ if (((mViewFlags & CLICKABLE) == CLICKABLE ||
+ (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
+ (event.getRepeatCount() == 0)) {
+ setPressed(true);
+ checkForLongClick(0);
+ return true;
}
}
return result;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b0f67ac..075719c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -228,6 +228,7 @@
InputStage mFirstInputStage;
InputStage mFirstPostImeInputStage;
+ SyntheticInputStage mSyntheticInputStage;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -589,8 +590,8 @@
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
- InputStage syntheticStage = new SyntheticInputStage();
- InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticStage);
+ mSyntheticInputStage = new SyntheticInputStage();
+ InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
@@ -3773,6 +3774,9 @@
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
+ // The synthetic stage occasionally needs to know about keys in order to debounce taps
+ mSyntheticInputStage.notifyKeyEvent(event);
+
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
@@ -3945,6 +3949,10 @@
}
super.onDeliverToNext(q);
}
+
+ public void notifyKeyEvent(KeyEvent e) {
+ mTouchNavigation.notifyKeyEvent(e);
+ }
}
/**
@@ -4375,6 +4383,9 @@
// Tap timeout in milliseconds.
private static final int TAP_TIMEOUT = 250;
+ // Debounce timeout for touch nav devices with a button under their pad, in milliseconds
+ private static final int DEBOUNCE_TIME = 250;
+
// The maximum distance traveled for a gesture to be considered a tap in millimeters.
private static final int TAP_SLOP_MILLIMETERS = 5;
@@ -4409,6 +4420,9 @@
private int mConfigTapTimeout;
private float mConfigTapSlop;
+ // Amount of time to wait between button presses and tap generation for debouncing
+ private int mConfigDebounceTime;
+
// The scaled tick distance. A movement of this amount should generally translate
// into a single dpad event in a given direction.
private float mConfigTickDistance;
@@ -4454,6 +4468,11 @@
private boolean mFlinging;
private float mFlingVelocity;
+ // The last time a confirm key was pressed on the touch nav device
+ private long mLastConfirmKeyTime = Long.MAX_VALUE;
+
+ private boolean mHasButtonUnderPad;
+
public SyntheticTouchNavigationHandler() {
super(true);
}
@@ -4497,6 +4516,8 @@
MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
mConfigMaxFlingVelocity =
MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+ mConfigDebounceTime = DEBOUNCE_TIME;
+ mHasButtonUnderPad = device.hasButtonUnderPad();
if (LOCAL_DEBUG) {
Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId
@@ -4567,10 +4588,13 @@
if (!mConsumedMovement
&& Math.hypot(mLastX - mStartX, mLastY - mStartY) < mConfigTapSlop
&& time <= mStartTime + mConfigTapTimeout) {
- // It's a tap!
- finishKeys(time);
- sendKeyDownOrRepeat(time, KeyEvent.KEYCODE_DPAD_CENTER, metaState);
- sendKeyUp(time);
+ if (!mHasButtonUnderPad ||
+ time >= mLastConfirmKeyTime + mConfigDebounceTime) {
+ // It's a tap!
+ finishKeys(time);
+ sendKeyDownOrRepeat(time, KeyEvent.KEYCODE_DPAD_CENTER, metaState);
+ sendKeyUp(time);
+ }
} else if (mConsumedMovement
&& mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
// It might be a fling.
@@ -4603,6 +4627,13 @@
}
}
+ public void notifyKeyEvent(KeyEvent e) {
+ final int keyCode = e.getKeyCode();
+ if (KeyEvent.isConfirmKey(e.getKeyCode())) {
+ mLastConfirmKeyTime = e.getDownTime();
+ }
+ }
+
private void finishKeys(long time) {
cancelFling();
sendKeyUp(time);