Add support for throttling motion events.

Change-Id: I24b3a17753e91ecda60a60fe5cd2e6b3260e033d
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index 2505cb0..aed4fa1 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -159,6 +159,12 @@
     virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
             int32_t injectorPid, int32_t injectorUid,
             Vector<InputTarget>& outTargets) = 0;
+
+    /* Gets the maximum suggested event delivery rate per second.
+     * This value is used to throttle motion event movement actions on a per-device
+     * basis.  It is not intended to be a hard limit.
+     */
+    virtual int32_t getMaxEventsPerSecond() = 0;
 };
 
 
@@ -332,6 +338,8 @@
         // Linked list of motion samples associated with this motion event.
         MotionSample firstSample;
         MotionSample* lastSample;
+
+        uint32_t countSamples() const;
     };
 
     // Tracks the progress of dispatching a particular event to a particular connection.
@@ -587,6 +595,17 @@
     Condition mInjectionSyncFinishedCondition;
     void decrementPendingSyncDispatchesLocked(EventEntry* entry);
 
+    // Throttling state.
+    struct ThrottleState {
+        nsecs_t minTimeBetweenEvents;
+
+        nsecs_t lastEventTime;
+        int32_t lastDeviceId;
+        uint32_t lastSource;
+
+        uint32_t originalSampleCount; // only collected during debugging
+    } mThrottleState;
+
     // Key repeat tracking.
     // XXX Move this up to the input reader instead.
     struct KeyRepeatState {
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index 13030b5..ce616a4 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -28,6 +28,9 @@
 // Log debug messages about input event injection.
 #define DEBUG_INJECTION 0
 
+// Log debug messages about input event throttling.
+#define DEBUG_THROTTLING 0
+
 #include <cutils/log.h>
 #include <ui/InputDispatcher.h>
 
@@ -66,6 +69,15 @@
 
     mKeyRepeatState.lastKeyEntry = NULL;
 
+    int32_t maxEventsPerSecond = policy->getMaxEventsPerSecond();
+    mThrottleState.minTimeBetweenEvents = 1000000000LL / maxEventsPerSecond;
+    mThrottleState.lastDeviceId = -1;
+
+#if DEBUG_THROTTLING
+    mThrottleState.originalSampleCount = 0;
+    LOGD("Throttling - Max events per second = %d", maxEventsPerSecond);
+#endif
+
     mCurrentInputTargetsValid = false;
 }
 
@@ -144,12 +156,60 @@
                 }
             } else {
                 // Inbound queue has at least one entry.
-                // Start processing it but leave it on the queue until later so that the
+                EventEntry* entry = mInboundQueue.head.next;
+
+                // Consider throttling the entry if it is a move event and there are no
+                // other events behind it in the queue.  Due to movement batching, additional
+                // samples may be appended to this event by the time the throttling timeout
+                // expires.
+                // TODO Make this smarter and consider throttling per device independently.
+                if (entry->type == EventEntry::TYPE_MOTION) {
+                    MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+                    int32_t deviceId = motionEntry->deviceId;
+                    uint32_t source = motionEntry->source;
+                    if (motionEntry->next == & mInboundQueue.tail
+                            && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+                            && deviceId == mThrottleState.lastDeviceId
+                            && source == mThrottleState.lastSource) {
+                        nsecs_t nextTime = mThrottleState.lastEventTime
+                                + mThrottleState.minTimeBetweenEvents;
+                        if (currentTime < nextTime) {
+                            // Throttle it!
+#if DEBUG_THROTTLING
+                            LOGD("Throttling - Delaying motion event for "
+                                    "device 0x%x, source 0x%08x by up to %0.3fms.",
+                                    deviceId, source, (nextTime - currentTime) * 0.000001);
+#endif
+                            if (nextTime < nextWakeupTime) {
+                                nextWakeupTime = nextTime;
+                            }
+                            if (mThrottleState.originalSampleCount == 0) {
+                                mThrottleState.originalSampleCount =
+                                        motionEntry->countSamples();
+                            }
+                            goto Throttle;
+                        }
+                    }
+
+#if DEBUG_THROTTLING
+                    if (mThrottleState.originalSampleCount != 0) {
+                        uint32_t count = motionEntry->countSamples();
+                        LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
+                                count - mThrottleState.originalSampleCount,
+                                mThrottleState.originalSampleCount, count);
+                        mThrottleState.originalSampleCount = 0;
+                    }
+#endif
+
+                    mThrottleState.lastEventTime = currentTime;
+                    mThrottleState.lastDeviceId = deviceId;
+                    mThrottleState.lastSource = source;
+                }
+
+                // Start processing the entry but leave it on the queue until later so that the
                 // input reader can keep appending samples onto a motion event between the
                 // time we started processing it and the time we finally enqueue dispatch
                 // entries for it.
-                EventEntry* entry = mInboundQueue.head.next;
-
                 switch (entry->type) {
                 case EventEntry::TYPE_CONFIGURATION_CHANGED: {
                     ConfigurationChangedEntry* typedEntry =
@@ -179,6 +239,8 @@
                 mInboundQueue.dequeue(entry);
                 mAllocator.releaseEventEntry(entry);
                 skipPoll = true;
+
+            Throttle: ;
             }
         }
 
@@ -192,8 +254,8 @@
         return;
     }
 
-    // Wait for callback or timeout or wake.
-    nsecs_t timeout = nanoseconds_to_milliseconds(nextWakeupTime - currentTime);
+    // Wait for callback or timeout or wake.  (make sure we round up, not down)
+    nsecs_t timeout = (nextWakeupTime - currentTime + 999999LL) / 1000000LL;
     int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0;
     mPollLoop->pollOnce(timeoutMillis);
 }
@@ -1708,6 +1770,16 @@
     motionEntry->lastSample = sample;
 }
 
+// --- InputDispatcher::MotionEntry ---
+
+uint32_t InputDispatcher::MotionEntry::countSamples() const {
+    uint32_t count = 1;
+    for (MotionSample* sample = firstSample.next; sample != NULL; sample = sample->next) {
+        count += 1;
+    }
+    return count;
+}
+
 // --- InputDispatcher::Connection ---
 
 InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index c2c799b..e54f183 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -26,6 +26,7 @@
 import android.os.Environment;
 import android.os.LocalPowerManager;
 import android.os.PowerManager;
+import android.os.SystemProperties;
 import android.util.Slog;
 import android.util.Xml;
 import android.view.InputChannel;
@@ -507,5 +508,18 @@
             
             return names.toArray(new String[names.size()]);
         }
+        
+        @SuppressWarnings("unused")
+        public int getMaxEventsPerSecond() {
+            int result = 0;
+            try {
+                result = Integer.parseInt(SystemProperties.get("windowsmgr.max_events_per_sec"));
+            } catch (NumberFormatException e) {
+            }
+            if (result < 1) {
+                result = 35;
+            }
+            return result;
+        }
     }
 }
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index ba58b43..59528db 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -139,6 +139,7 @@
     jmethodID filterJumpyTouchEvents;
     jmethodID getVirtualKeyDefinitions;
     jmethodID getExcludedDeviceNames;
+    jmethodID getMaxEventsPerSecond;
 } gCallbacksClassInfo;
 
 static struct {
@@ -249,6 +250,7 @@
             int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
     virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
             int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
+    virtual int32_t getMaxEventsPerSecond();
 
 private:
     struct InputWindow {
@@ -310,6 +312,9 @@
     int32_t mFilterTouchEvents;
     int32_t mFilterJumpyTouchEvents;
 
+    // Cached throttling policy.
+    int32_t mMaxEventsPerSecond;
+
     // Cached display state.  (lock mDisplayLock)
     Mutex mDisplayLock;
     int32_t mDisplayWidth, mDisplayHeight;
@@ -400,6 +405,7 @@
 
 NativeInputManager::NativeInputManager(jobject callbacksObj) :
     mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1),
+    mMaxEventsPerSecond(-1),
     mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0),
     mDispatchEnabled(true), mDispatchFrozen(false), mWindowsReady(true),
     mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL),
@@ -921,6 +927,21 @@
     }
 }
 
+int32_t NativeInputManager::getMaxEventsPerSecond() {
+    if (mMaxEventsPerSecond < 0) {
+        JNIEnv* env = jniEnv();
+
+        jint result = env->CallIntMethod(mCallbacksObj,
+                gCallbacksClassInfo.getMaxEventsPerSecond);
+        if (checkAndClearExceptionFromCallback(env, "getMaxEventsPerSecond")) {
+            result = 35;
+        }
+
+        mMaxEventsPerSecond = result;
+    }
+    return mMaxEventsPerSecond;
+}
+
 void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) {
 #if DEBUG_FOCUS
     LOGD("setInputWindows");
@@ -2293,6 +2314,9 @@
     GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz,
             "getExcludedDeviceNames", "()[Ljava/lang/String;");
 
+    GET_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, gCallbacksClassInfo.clazz,
+            "getMaxEventsPerSecond", "()I");
+
     // VirtualKeyDefinition
 
     FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz,