summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2010-08-18 15:51:08 -0700
committerJeff Brown <jeffbrown@google.com>2010-08-18 16:58:27 -0700
commitae9fc03bdccda709101291bbcd3beaa5b6daebfc (patch)
tree69367f760a943f9eda3edabcb1130302ea6cb0d8
parent6b2c56a299eb05deef2b887549e29e9e9f0f2cf6 (diff)
downloadframeworks_base-ae9fc03bdccda709101291bbcd3beaa5b6daebfc.zip
frameworks_base-ae9fc03bdccda709101291bbcd3beaa5b6daebfc.tar.gz
frameworks_base-ae9fc03bdccda709101291bbcd3beaa5b6daebfc.tar.bz2
Add support for throttling motion events.
Change-Id: I24b3a17753e91ecda60a60fe5cd2e6b3260e033d
-rw-r--r--include/ui/InputDispatcher.h19
-rw-r--r--libs/ui/InputDispatcher.cpp82
-rw-r--r--services/java/com/android/server/InputManager.java14
-rw-r--r--services/jni/com_android_server_InputManager.cpp24
4 files changed, 134 insertions, 5 deletions
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 @@ public:
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 @@ private:
// 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 @@ private:
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 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic
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 @@ void InputDispatcher::dispatchOnce() {
}
} 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 @@ void InputDispatcher::dispatchOnce() {
mInboundQueue.dequeue(entry);
mAllocator.releaseEventEntry(entry);
skipPoll = true;
+
+ Throttle: ;
}
}
@@ -192,8 +254,8 @@ void InputDispatcher::dispatchOnce() {
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 @@ void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
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.content.res.Configuration;
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 @@ public class InputManager {
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 @@ static struct {
jmethodID filterJumpyTouchEvents;
jmethodID getVirtualKeyDefinitions;
jmethodID getExcludedDeviceNames;
+ jmethodID getMaxEventsPerSecond;
} gCallbacksClassInfo;
static struct {
@@ -249,6 +250,7 @@ public:
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 @@ private:
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 @@ private:
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 @@ nsecs_t NativeInputManager::getKeyRepeatTimeout() {
}
}
+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 @@ int register_android_server_InputManager(JNIEnv* env) {
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,