diff options
| -rw-r--r-- | include/ui/InputDispatcher.h | 19 | ||||
| -rw-r--r-- | libs/ui/InputDispatcher.cpp | 82 | ||||
| -rw-r--r-- | services/java/com/android/server/InputManager.java | 14 | ||||
| -rw-r--r-- | services/jni/com_android_server_InputManager.cpp | 24 | 
4 files changed, 134 insertions, 5 deletions
| diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h index 2505cb0a89b4..aed4fa112b14 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 13030b56e01f..ce616a475c42 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 c2c799b4e0a0..e54f1837c8c8 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 ba58b43e2df4..59528dba9174 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, |