Filter virtual keys after touches.

Adds a new virtualKeyQuietTimeMillis configuration resource that sets
the duration for which virtual keys will be dropped after recent touches
on screen.  The default value is 0; it is intended to be overridden
per device using a resource overlay.

This change is designed to help in two cases:

1. Swipes from touchscreen into virtual key area.
2. Accidental taps in virtual key area while using on-screen keyboard.

Bug: 3089163
Change-Id: Ib912d4f8a4df9966a39cd537d3ec7c24afab7225
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6b66791..8e9a5a4 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -121,7 +121,7 @@
         const sp<InputReaderPolicyInterface>& policy,
         const sp<InputDispatcherInterface>& dispatcher) :
         mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
-        mGlobalMetaState(0) {
+        mGlobalMetaState(0), mDisableVirtualKeysTimeout(-1) {
     configureExcludedDevices();
     updateGlobalMetaState();
     updateInputConfiguration();
@@ -373,6 +373,24 @@
     } // release state lock
 }
 
+void InputReader::disableVirtualKeysUntil(nsecs_t time) {
+    mDisableVirtualKeysTimeout = time;
+}
+
+bool InputReader::shouldDropVirtualKey(nsecs_t now,
+        InputDevice* device, int32_t keyCode, int32_t scanCode) {
+    if (now < mDisableVirtualKeysTimeout) {
+        LOGI("Dropping virtual key from device %s because virtual keys are "
+                "temporarily disabled for the next %0.3fms.  keyCode=%d, scanCode=%d",
+                device->getName().string(),
+                (mDisableVirtualKeysTimeout - now) * 0.000001,
+                keyCode, scanCode);
+        return true;
+    } else {
+        return false;
+    }
+}
+
 void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
     { // acquire state lock
         AutoMutex _l(mStateLock);
@@ -889,6 +907,12 @@
                 keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
             } else {
                 // key down
+                if ((policyFlags & POLICY_FLAG_VIRTUAL)
+                        && mContext->shouldDropVirtualKey(when,
+                                getDevice(), keyCode, scanCode)) {
+                    return;
+                }
+
                 mLocked.keyDowns.push();
                 KeyDown& keyDown = mLocked.keyDowns.editTop();
                 keyDown.keyCode = keyCode;
@@ -1428,6 +1452,7 @@
     mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents();
     mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents();
     mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
+    mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime();
 
     String8 deviceTypeString;
     mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
@@ -2219,6 +2244,7 @@
 
     TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
     if (touchResult == DISPATCH_TOUCH) {
+        detectGestures(when);
         dispatchTouches(when, policyFlags);
     }
 
@@ -2304,6 +2330,11 @@
                     if (mCurrentTouch.pointerCount == 1) {
                         const VirtualKey* virtualKey = findVirtualKeyHitLocked(x, y);
                         if (virtualKey) {
+                            if (mContext->shouldDropVirtualKey(when, getDevice(),
+                                    virtualKey->keyCode, virtualKey->scanCode)) {
+                                return DROP_STROKE;
+                            }
+
                             mLocked.currentVirtualKey.down = true;
                             mLocked.currentVirtualKey.downTime = when;
                             mLocked.currentVirtualKey.keyCode = virtualKey->keyCode;
@@ -2341,6 +2372,26 @@
     return touchResult;
 }
 
+void TouchInputMapper::detectGestures(nsecs_t when) {
+    // Disable all virtual key touches that happen within a short time interval of the
+    // most recent touch.  The idea is to filter out stray virtual key presses when
+    // interacting with the touch screen.
+    //
+    // Problems we're trying to solve:
+    //
+    // 1. While scrolling a list or dragging the window shade, the user swipes down into a
+    //    virtual key area that is implemented by a separate touch panel and accidentally
+    //    triggers a virtual key.
+    //
+    // 2. While typing in the on screen keyboard, the user taps slightly outside the screen
+    //    area and accidentally triggers a virtual key.  This often happens when virtual keys
+    //    are layed out below the screen near to where the on screen keyboard's space bar
+    //    is displayed.
+    if (mParameters.virtualKeyQuietTime > 0 && mCurrentTouch.pointerCount != 0) {
+        mContext->disableVirtualKeysUntil(when + mParameters.virtualKeyQuietTime);
+    }
+}
+
 void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
     uint32_t currentPointerCount = mCurrentTouch.pointerCount;
     uint32_t lastPointerCount = mLastTouch.pointerCount;
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 8b2d40a..7619682 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -78,6 +78,12 @@
      */
     virtual bool filterJumpyTouchEvents() = 0;
 
+    /* Gets the amount of time to disable virtual keys after the screen is touched
+     * in order to filter out accidental virtual key presses due to swiping gestures
+     * or taps near the edge of the display.  May be 0 to disable the feature.
+     */
+    virtual nsecs_t getVirtualKeyQuietTime() = 0;
+
     /* Gets the excluded device names for the platform. */
     virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) = 0;
 
@@ -147,6 +153,10 @@
     virtual void updateGlobalMetaState() = 0;
     virtual int32_t getGlobalMetaState() = 0;
 
+    virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
+    virtual bool shouldDropVirtualKey(nsecs_t now,
+            InputDevice* device, int32_t keyCode, int32_t scanCode) = 0;
+
     virtual InputReaderPolicyInterface* getPolicy() = 0;
     virtual InputDispatcherInterface* getDispatcher() = 0;
     virtual EventHubInterface* getEventHub() = 0;
@@ -234,6 +244,11 @@
     InputConfiguration mInputConfiguration;
     void updateInputConfiguration();
 
+    nsecs_t mDisableVirtualKeysTimeout;
+    virtual void disableVirtualKeysUntil(nsecs_t time);
+    virtual bool shouldDropVirtualKey(nsecs_t now,
+            InputDevice* device, int32_t keyCode, int32_t scanCode);
+
     // state queries
     typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
     int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
@@ -603,6 +618,7 @@
         bool useBadTouchFilter;
         bool useJumpyTouchFilter;
         bool useAveragingTouchFilter;
+        nsecs_t virtualKeyQuietTime;
     } mParameters;
 
     // Immutable calibration parameters in parsed form.
@@ -839,6 +855,7 @@
     void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch,
             BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
             int32_t motionEventAction);
+    void detectGestures(nsecs_t when);
 
     bool isPointInsideSurfaceLocked(int32_t x, int32_t y);
     const VirtualKey* findVirtualKeyHitLocked(int32_t x, int32_t y);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 8ec6f53..775747c 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -127,6 +127,10 @@
         mFilterJumpyTouchEvents = enabled;
     }
 
+    virtual nsecs_t getVirtualKeyQuietTime() {
+        return 0;
+    }
+
     void addExcludedDeviceName(const String8& deviceName) {
         mExcludedDeviceNames.push(deviceName);
     }
@@ -722,6 +726,14 @@
     virtual InputDispatcherInterface* getDispatcher() {
         return mDispatcher.get();
     }
+
+    virtual void disableVirtualKeysUntil(nsecs_t time) {
+    }
+
+    virtual bool shouldDropVirtualKey(nsecs_t now,
+            InputDevice* device, int32_t keyCode, int32_t scanCode) {
+        return false;
+    }
 };
 
 
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index 06595ae..8d249ff 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -481,7 +481,13 @@
             return mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_filterJumpyTouchEvents);
         }
-        
+
+        @SuppressWarnings("unused")
+        public int getVirtualKeyQuietTimeMillis() {
+            return mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_virtualKeyQuietTimeMillis);
+        }
+
         @SuppressWarnings("unused")
         public String[] getExcludedDeviceNames() {
             ArrayList<String> names = new ArrayList<String>();
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index e04e4c3..5b329bb 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -62,6 +62,7 @@
     jmethodID checkInjectEventsPermission;
     jmethodID filterTouchEvents;
     jmethodID filterJumpyTouchEvents;
+    jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
     jmethodID getMaxEventsPerSecond;
     jmethodID getPointerLayer;
@@ -159,6 +160,7 @@
             int32_t* width, int32_t* height, int32_t* orientation);
     virtual bool filterTouchEvents();
     virtual bool filterJumpyTouchEvents();
+    virtual nsecs_t getVirtualKeyQuietTime();
     virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
 
@@ -191,6 +193,7 @@
     // Cached filtering policies.
     int32_t mFilterTouchEvents;
     int32_t mFilterJumpyTouchEvents;
+    nsecs_t mVirtualKeyQuietTime;
 
     // Cached throttling policy.
     int32_t mMaxEventsPerSecond;
@@ -219,7 +222,7 @@
 
 
 NativeInputManager::NativeInputManager(jobject callbacksObj) :
-    mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1),
+    mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
     mMaxEventsPerSecond(-1) {
     JNIEnv* env = jniEnv();
 
@@ -355,6 +358,24 @@
     return mFilterJumpyTouchEvents;
 }
 
+nsecs_t NativeInputManager::getVirtualKeyQuietTime() {
+    if (mVirtualKeyQuietTime < 0) {
+        JNIEnv* env = jniEnv();
+
+        jint result = env->CallIntMethod(mCallbacksObj,
+                gCallbacksClassInfo.getVirtualKeyQuietTimeMillis);
+        if (checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {
+            result = 0;
+        }
+        if (result < 0) {
+            result = 0;
+        }
+
+        mVirtualKeyQuietTime = milliseconds_to_nanoseconds(result);
+    }
+    return mVirtualKeyQuietTime;
+}
+
 void NativeInputManager::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
     outExcludedDeviceNames.clear();
 
@@ -1155,6 +1176,9 @@
     GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, gCallbacksClassInfo.clazz,
             "filterJumpyTouchEvents", "()Z");
 
+    GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyQuietTimeMillis, gCallbacksClassInfo.clazz,
+            "getVirtualKeyQuietTimeMillis", "()I");
+
     GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz,
             "getExcludedDeviceNames", "()[Ljava/lang/String;");