diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 12253048005a0d9616a1b7675dac02e8f8de449e..0f2a014f1ccd7794488c39364f0a47aa7ecb7811 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -41,6 +41,8 @@
     <integer name="config_keyboard_grid_height">16</integer>
     <integer name="config_long_press_key_timeout">400</integer>
     <integer name="config_long_press_shift_key_timeout">1200</integer>
+    <integer name="config_touch_noise_threshold_millis">40</integer>
+    <dimen name="config_touch_noise_threshold_distance">2.0mm</dimen>
     <string name="config_text_size_of_language_on_spacebar">small</string>
     <integer name="config_max_popup_keyboard_column">9</integer>
     <!-- Whether or not auto-correction should be enabled by default -->
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 552ab528b9b7551dccfec362227e5ec79efa3156..c07035d622bb191695c3b4178884285cb9269396 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -56,6 +56,9 @@ public class PointerTracker {
     private final boolean mHasDistinctMultitouch;
     private final boolean mConfigSlidingKeyInputEnabled;
 
+    private final int mTouchNoiseThresholdMillis;
+    private final int mTouchNoiseThresholdDistanceSquared;
+
     private Keyboard mKeyboard;
     private Key[] mKeys;
     private int mKeyHysteresisDistanceSquared = -1;
@@ -108,6 +111,11 @@ public class PointerTracker {
         mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
         mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
         mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout);
+        mTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis);
+        final float touchNoiseThresholdDistance = res.getDimension(
+                R.dimen.config_touch_noise_threshold_distance);
+        mTouchNoiseThresholdDistanceSquared = (int)(
+                touchNoiseThresholdDistance * touchNoiseThresholdDistance);
     }
 
     public void setOnKeyboardActionListener(KeyboardActionListener listener) {
@@ -253,6 +261,24 @@ public class PointerTracker {
         if (DEBUG_EVENT)
             printTouchEvent("onDownEvent:", x, y, eventTime);
 
+        // TODO: up-to-down filter, if (down-up) is less than threshold, removeMessage(UP, this) in
+        // Handler, and just ignore this down event.
+        // TODO: down-to-up filter, just record down time. do not enqueue pointer now.
+
+        // Naive up-to-down noise filter.
+        final long deltaT = eventTime - mKeyState.getUpTime();
+        if (deltaT < mTouchNoiseThresholdMillis) {
+            final int dx = x - mKeyState.getLastX();
+            final int dy = y - mKeyState.getLastY();
+            final int distanceSquared = (dx * dx + dy * dy);
+            if (distanceSquared < mTouchNoiseThresholdDistanceSquared) {
+                Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
+                        + " distance=" + distanceSquared);
+                setAlreadyProcessed();
+                return;
+            }
+        }
+
         if (queue != null) {
             if (isOnModifierKey(x, y)) {
                 // Before processing a down event of modifier key, all pointers already being
@@ -264,7 +290,7 @@ public class PointerTracker {
         onDownEventInternal(x, y, eventTime);
     }
 
-    public void onDownEventInternal(int x, int y, long eventTime) {
+    private void onDownEventInternal(int x, int y, long eventTime) {
         int keyIndex = mKeyState.onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
         // from modifier key, or 3) this pointer is on mini-keyboard.
@@ -297,6 +323,10 @@ public class PointerTracker {
         if (mKeyAlreadyProcessed)
             return;
         final PointerTrackerKeyState keyState = mKeyState;
+
+        // TODO: down-to-up filter, if (eventTime-downTime) is less than threshold, just ignore
+        // this move event. Otherwise fire {@link onDownEventInternal} and continue.
+
         final int keyIndex = keyState.onMoveKey(x, y);
         final Key oldKey = getKey(keyState.getKeyIndex());
         if (isValidKeyIndex(keyIndex)) {
@@ -342,11 +372,17 @@ public class PointerTracker {
         showKeyPreviewAndUpdateKeyGraphics(mKeyState.getKeyIndex());
     }
 
+    // TODO: up-to-down filter, if delayed UP message is fired, invoke {@link onUpEventInternal}.
+
     public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
         if (ENABLE_ASSERTION) checkAssertion(queue);
         if (DEBUG_EVENT)
             printTouchEvent("onUpEvent  :", x, y, eventTime);
 
+        // TODO: up-to-down filter, just sendDelayedMessage(UP, this) to Handler.
+        // TODO: down-to-up filter, if (eventTime-downTime) is less than threshold, just ignore
+        // this up event. Otherwise fire {@link onDownEventInternal} and {@link onUpEventInternal}.
+
         if (queue != null) {
             if (isModifier()) {
                 // Before processing an up event of modifier key, all pointers already being
@@ -374,7 +410,7 @@ public class PointerTracker {
         if (mKeyAlreadyProcessed)
             return;
         final PointerTrackerKeyState keyState = mKeyState;
-        int keyIndex = keyState.onUpKey(x, y);
+        int keyIndex = keyState.onUpKey(x, y, eventTime);
         if (isMinorMoveBounce(x, y, keyIndex)) {
             // Use previous fixed key index and coordinates.
             keyIndex = keyState.getKeyIndex();
@@ -396,10 +432,10 @@ public class PointerTracker {
 
         if (queue != null)
             queue.remove(this);
-        onCancelEventInternal(x, y, eventTime);
+        onCancelEventInternal();
     }
 
-    private void onCancelEventInternal(int x, int y, long eventTime) {
+    private void onCancelEventInternal() {
         mHandler.cancelKeyTimers();
         mHandler.cancelPopupPreview();
         showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
index b9ae03a59b0fcc0d10f4555180a9d4eb5313a9a6..8b969c70acab17e6d6c0b51f3e480c51e4f6873c 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
@@ -26,6 +26,7 @@ package com.android.inputmethod.keyboard;
     private int mStartX;
     private int mStartY;
     private long mDownTime;
+    private long mUpTime;
 
     // The current key index where this pointer is.
     private int mKeyIndex = KeyDetector.NOT_A_KEY;
@@ -65,6 +66,10 @@ package com.android.inputmethod.keyboard;
         return mDownTime;
     }
 
+    public long getUpTime() {
+        return mUpTime;
+    }
+
     public int getLastX() {
         return mLastX;
     }
@@ -77,7 +82,6 @@ package com.android.inputmethod.keyboard;
         mStartX = x;
         mStartY = y;
         mDownTime = eventTime;
-
         return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
     }
 
@@ -98,7 +102,8 @@ package com.android.inputmethod.keyboard;
         return keyIndex;
     }
 
-    public int onUpKey(int x, int y) {
+    public int onUpKey(int x, int y, long eventTime) {
+        mUpTime = eventTime;
         return onMoveKeyInternal(x, y);
     }