Handle stylus buttons and tool types.

Added TOOL_TYPE_ERASER.

Refactored the InputReader to share more code between the
various input mappers that handle button states and to
simplify the accumulator implementations by having each
one only handle a single type of input.

Removed the concept of direct/indirect tool types from the API.
If we add it back, it should be done in a manner that is orthogonal
to the tool type itself, perhaps as a flags field on the pointer.
The device source may well provide sufficient information anyhow.

Change-Id: I811c22d95e8304269b6ee4f6d11a6b04f3cfc1b2
diff --git a/api/current.txt b/api/current.txt
index db7713a..dfc1c47 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21919,9 +21919,8 @@
     field public static final int EDGE_TOP = 1; // 0x1
     field public static final int FLAG_WINDOW_IS_OBSCURED = 1; // 0x1
     field public static final int INVALID_POINTER_ID = -1; // 0xffffffff
+    field public static final int TOOL_TYPE_ERASER = 4; // 0x4
     field public static final int TOOL_TYPE_FINGER = 1; // 0x1
-    field public static final int TOOL_TYPE_INDIRECT_FINGER = 4; // 0x4
-    field public static final int TOOL_TYPE_INDIRECT_STYLUS = 5; // 0x5
     field public static final int TOOL_TYPE_MOUSE = 3; // 0x3
     field public static final int TOOL_TYPE_STYLUS = 2; // 0x2
     field public static final int TOOL_TYPE_UNKNOWN = 0; // 0x0
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 88f59d4..da5c7b2 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1123,7 +1123,10 @@
     }
 
     /**
-     * Button constant: Primary button (left mouse button, stylus tip).
+     * Button constant: Primary button (left mouse button).
+     *
+     * This button constant is not set in response to simple touches with a finger
+     * or stylus tip.  The user must actually push a button.
      *
      * @see #getButtonState
      */
@@ -1215,55 +1218,32 @@
     public static final int TOOL_TYPE_UNKNOWN = 0;
 
     /**
-     * Tool type constant: The tool is a finger directly touching the display.
-     *
-     * This is a <em>direct</em> positioning tool.
+     * Tool type constant: The tool is a finger.
      *
      * @see #getToolType
      */
     public static final int TOOL_TYPE_FINGER = 1;
 
     /**
-     * Tool type constant: The tool is a stylus directly touching the display
-     * or hovering slightly above it.
-     *
-     * This is a <em>direct</em> positioning tool.
+     * Tool type constant: The tool is a stylus.
      *
      * @see #getToolType
      */
     public static final int TOOL_TYPE_STYLUS = 2;
 
     /**
-     * Tool type constant: The tool is a mouse or trackpad that translates
-     * relative motions into cursor movements on the display.
-     *
-     * This is an <em>indirect</em> positioning tool.
+     * Tool type constant: The tool is a mouse or trackpad.
      *
      * @see #getToolType
      */
     public static final int TOOL_TYPE_MOUSE = 3;
 
     /**
-     * Tool type constant: The tool is a finger on a touch pad that is not
-     * directly attached to the display.  Finger movements on the touch pad
-     * may be translated into touches on the display, possibly with visual feedback.
-     *
-     * This is an <em>indirect</em> positioning tool.
+     * Tool type constant: The tool is an eraser or a stylus being used in an inverted posture.
      *
      * @see #getToolType
      */
-    public static final int TOOL_TYPE_INDIRECT_FINGER = 4;
-
-    /**
-     * Tool type constant: The tool is a stylus on a digitizer tablet that is not
-     * attached to the display.  Stylus movements on the digitizer may be translated
-     * into touches on the display, possibly with visual feedback.
-     *
-     * This is an <em>indirect</em> positioning tool.
-     *
-     * @see #getToolType
-     */
-    public static final int TOOL_TYPE_INDIRECT_STYLUS = 5;
+    public static final int TOOL_TYPE_ERASER = 4;
 
     // NOTE: If you add a new tool type here you must also add it to:
     //  native/include/android/input.h
@@ -1276,8 +1256,7 @@
         names.append(TOOL_TYPE_FINGER, "TOOL_TYPE_FINGER");
         names.append(TOOL_TYPE_STYLUS, "TOOL_TYPE_STYLUS");
         names.append(TOOL_TYPE_MOUSE, "TOOL_TYPE_MOUSE");
-        names.append(TOOL_TYPE_INDIRECT_FINGER, "TOOL_TYPE_INDIRECT_FINGER");
-        names.append(TOOL_TYPE_INDIRECT_STYLUS, "TOOL_TYPE_INDIRECT_STYLUS");
+        names.append(TOOL_TYPE_ERASER, "TOOL_TYPE_ERASER");
     }
 
     // Private value for history pos that obtains the current sample.
diff --git a/native/include/android/input.h b/native/include/android/input.h
index 0d8ea28..7a0dcd3 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -415,8 +415,7 @@
     AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
     AMOTION_EVENT_TOOL_TYPE_STYLUS = 2,
     AMOTION_EVENT_TOOL_TYPE_MOUSE = 3,
-    AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER = 4,
-    AMOTION_EVENT_TOOL_TYPE_INDIRECT_STYLUS = 5,
+    AMOTION_EVENT_TOOL_TYPE_ERASER = 4,
 };
 
 /*
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index ca2540b..7ea3de2 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -460,6 +460,17 @@
     mExcludedDevices = devices;
 }
 
+bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
+    AutoMutex _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device && scanCode >= 0 && scanCode <= KEY_MAX) {
+        if (test_bit(scanCode, device->keyBitmask)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
     AutoMutex _l(mLock);
     Device* device = getDeviceLocked(deviceId);
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index 695dfdf..293a1a0 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -195,6 +195,7 @@
     virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
             uint8_t* outFlags) const = 0;
 
+    virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0;
     virtual bool hasLed(int32_t deviceId, int32_t led) const = 0;
     virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0;
 
@@ -246,6 +247,7 @@
 
     virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize);
 
+    virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const;
     virtual bool hasLed(int32_t deviceId, int32_t led) const;
     virtual void setLedState(int32_t deviceId, int32_t led, bool on);
 
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 3e041f3..9d69c60 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -151,30 +151,6 @@
     return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0;
 }
 
-static uint32_t getButtonStateForScanCode(int32_t scanCode) {
-    // Currently all buttons are mapped to the primary button.
-    switch (scanCode) {
-    case BTN_LEFT:
-        return AMOTION_EVENT_BUTTON_PRIMARY;
-    case BTN_RIGHT:
-    case BTN_STYLUS:
-        return AMOTION_EVENT_BUTTON_SECONDARY;
-    case BTN_MIDDLE:
-    case BTN_STYLUS2:
-        return AMOTION_EVENT_BUTTON_TERTIARY;
-    case BTN_SIDE:
-        return AMOTION_EVENT_BUTTON_BACK;
-    case BTN_FORWARD:
-    case BTN_EXTRA:
-        return AMOTION_EVENT_BUTTON_FORWARD;
-    case BTN_BACK:
-        return AMOTION_EVENT_BUTTON_BACK;
-    case BTN_TASK:
-    default:
-        return 0;
-    }
-}
-
 // Returns true if the pointer should be reported as being down given the specified
 // button states.  This determines whether the event is reported as a touch event.
 static bool isPointerDown(int32_t buttonState) {
@@ -1007,6 +983,378 @@
 }
 
 
+// --- CursorButtonAccumulator ---
+
+CursorButtonAccumulator::CursorButtonAccumulator() {
+    clearButtons();
+}
+
+void CursorButtonAccumulator::clearButtons() {
+    mBtnLeft = 0;
+    mBtnRight = 0;
+    mBtnMiddle = 0;
+    mBtnBack = 0;
+    mBtnSide = 0;
+    mBtnForward = 0;
+    mBtnExtra = 0;
+    mBtnTask = 0;
+}
+
+void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_KEY) {
+        switch (rawEvent->scanCode) {
+        case BTN_LEFT:
+            mBtnLeft = rawEvent->value;
+            break;
+        case BTN_RIGHT:
+            mBtnRight = rawEvent->value;
+            break;
+        case BTN_MIDDLE:
+            mBtnMiddle = rawEvent->value;
+            break;
+        case BTN_BACK:
+            mBtnBack = rawEvent->value;
+            break;
+        case BTN_SIDE:
+            mBtnSide = rawEvent->value;
+            break;
+        case BTN_FORWARD:
+            mBtnForward = rawEvent->value;
+            break;
+        case BTN_EXTRA:
+            mBtnExtra = rawEvent->value;
+            break;
+        case BTN_TASK:
+            mBtnTask = rawEvent->value;
+            break;
+        }
+    }
+}
+
+uint32_t CursorButtonAccumulator::getButtonState() const {
+    uint32_t result = 0;
+    if (mBtnLeft) {
+        result |= AMOTION_EVENT_BUTTON_PRIMARY;
+    }
+    if (mBtnRight) {
+        result |= AMOTION_EVENT_BUTTON_SECONDARY;
+    }
+    if (mBtnMiddle) {
+        result |= AMOTION_EVENT_BUTTON_TERTIARY;
+    }
+    if (mBtnBack || mBtnSide) {
+        result |= AMOTION_EVENT_BUTTON_BACK;
+    }
+    if (mBtnForward || mBtnExtra) {
+        result |= AMOTION_EVENT_BUTTON_FORWARD;
+    }
+    return result;
+}
+
+
+// --- CursorMotionAccumulator ---
+
+CursorMotionAccumulator::CursorMotionAccumulator() :
+        mHaveRelWheel(false), mHaveRelHWheel(false) {
+    clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::configure(InputDevice* device) {
+    mHaveRelWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_WHEEL);
+    mHaveRelHWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_HWHEEL);
+}
+
+void CursorMotionAccumulator::clearRelativeAxes() {
+    mRelX = 0;
+    mRelY = 0;
+    mRelWheel = 0;
+    mRelHWheel = 0;
+}
+
+void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_REL) {
+        switch (rawEvent->scanCode) {
+        case REL_X:
+            mRelX = rawEvent->value;
+            break;
+        case REL_Y:
+            mRelY = rawEvent->value;
+            break;
+        case REL_WHEEL:
+            mRelWheel = rawEvent->value;
+            break;
+        case REL_HWHEEL:
+            mRelHWheel = rawEvent->value;
+            break;
+        }
+    }
+}
+
+
+// --- TouchButtonAccumulator ---
+
+TouchButtonAccumulator::TouchButtonAccumulator() :
+        mHaveBtnTouch(false) {
+    clearButtons();
+}
+
+void TouchButtonAccumulator::configure(InputDevice* device) {
+    mHaveBtnTouch = device->getEventHub()->hasScanCode(device->getId(), BTN_TOUCH);
+}
+
+void TouchButtonAccumulator::clearButtons() {
+    mBtnTouch = 0;
+    mBtnStylus = 0;
+    mBtnStylus2 = 0;
+    mBtnToolFinger = 0;
+    mBtnToolPen = 0;
+    mBtnToolRubber = 0;
+}
+
+void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_KEY) {
+        switch (rawEvent->scanCode) {
+        case BTN_TOUCH:
+            mBtnTouch = rawEvent->value;
+            break;
+        case BTN_STYLUS:
+            mBtnStylus = rawEvent->value;
+            break;
+        case BTN_STYLUS2:
+            mBtnStylus2 = rawEvent->value;
+            break;
+        case BTN_TOOL_FINGER:
+            mBtnToolFinger = rawEvent->value;
+            break;
+        case BTN_TOOL_PEN:
+            mBtnToolPen = rawEvent->value;
+            break;
+        case BTN_TOOL_RUBBER:
+            mBtnToolRubber = rawEvent->value;
+            break;
+        }
+    }
+}
+
+uint32_t TouchButtonAccumulator::getButtonState() const {
+    uint32_t result = 0;
+    if (mBtnStylus) {
+        result |= AMOTION_EVENT_BUTTON_SECONDARY;
+    }
+    if (mBtnStylus2) {
+        result |= AMOTION_EVENT_BUTTON_TERTIARY;
+    }
+    return result;
+}
+
+int32_t TouchButtonAccumulator::getToolType() const {
+    if (mBtnToolRubber) {
+        return AMOTION_EVENT_TOOL_TYPE_ERASER;
+    }
+    if (mBtnToolPen) {
+        return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    }
+    if (mBtnToolFinger) {
+        return AMOTION_EVENT_TOOL_TYPE_FINGER;
+    }
+    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+bool TouchButtonAccumulator::isActive() const {
+    return mBtnTouch || mBtnToolFinger || mBtnToolPen
+            || mBtnToolRubber || mBtnStylus || mBtnStylus2;
+}
+
+bool TouchButtonAccumulator::isHovering() const {
+    return mHaveBtnTouch && !mBtnTouch;
+}
+
+
+// --- SingleTouchMotionAccumulator ---
+
+SingleTouchMotionAccumulator::SingleTouchMotionAccumulator() {
+    clearAbsoluteAxes();
+}
+
+void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
+    mAbsX = 0;
+    mAbsY = 0;
+    mAbsPressure = 0;
+    mAbsToolWidth = 0;
+    mAbsDistance = 0;
+}
+
+void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_ABS) {
+        switch (rawEvent->scanCode) {
+        case ABS_X:
+            mAbsX = rawEvent->value;
+            break;
+        case ABS_Y:
+            mAbsY = rawEvent->value;
+            break;
+        case ABS_PRESSURE:
+            mAbsPressure = rawEvent->value;
+            break;
+        case ABS_TOOL_WIDTH:
+            mAbsToolWidth = rawEvent->value;
+            break;
+        case ABS_DISTANCE:
+            mAbsDistance = rawEvent->value;
+            break;
+        }
+    }
+}
+
+
+// --- MultiTouchMotionAccumulator ---
+
+MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() :
+        mCurrentSlot(-1), mSlots(NULL), mSlotCount(0), mUsingSlotsProtocol(false) {
+}
+
+MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() {
+    delete[] mSlots;
+}
+
+void MultiTouchMotionAccumulator::configure(size_t slotCount, bool usingSlotsProtocol) {
+    mSlotCount = slotCount;
+    mUsingSlotsProtocol = usingSlotsProtocol;
+
+    delete[] mSlots;
+    mSlots = new Slot[slotCount];
+}
+
+void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
+    for (size_t i = 0; i < mSlotCount; i++) {
+        mSlots[i].clearIfInUse();
+    }
+    mCurrentSlot = initialSlot;
+}
+
+void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_ABS) {
+        bool newSlot = false;
+        if (mUsingSlotsProtocol) {
+            if (rawEvent->scanCode == ABS_MT_SLOT) {
+                mCurrentSlot = rawEvent->value;
+                newSlot = true;
+            }
+        } else if (mCurrentSlot < 0) {
+            mCurrentSlot = 0;
+        }
+
+        if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
+#if DEBUG_POINTERS
+            if (newSlot) {
+                LOGW("MultiTouch device emitted invalid slot index %d but it "
+                        "should be between 0 and %d; ignoring this slot.",
+                        mCurrentSlot, mSlotCount - 1);
+            }
+#endif
+        } else {
+            Slot* slot = &mSlots[mCurrentSlot];
+
+            switch (rawEvent->scanCode) {
+            case ABS_MT_POSITION_X:
+                slot->mInUse = true;
+                slot->mAbsMTPositionX = rawEvent->value;
+                break;
+            case ABS_MT_POSITION_Y:
+                slot->mInUse = true;
+                slot->mAbsMTPositionY = rawEvent->value;
+                break;
+            case ABS_MT_TOUCH_MAJOR:
+                slot->mInUse = true;
+                slot->mAbsMTTouchMajor = rawEvent->value;
+                break;
+            case ABS_MT_TOUCH_MINOR:
+                slot->mInUse = true;
+                slot->mAbsMTTouchMinor = rawEvent->value;
+                slot->mHaveAbsMTTouchMinor = true;
+                break;
+            case ABS_MT_WIDTH_MAJOR:
+                slot->mInUse = true;
+                slot->mAbsMTWidthMajor = rawEvent->value;
+                break;
+            case ABS_MT_WIDTH_MINOR:
+                slot->mInUse = true;
+                slot->mAbsMTWidthMinor = rawEvent->value;
+                slot->mHaveAbsMTWidthMinor = true;
+                break;
+            case ABS_MT_ORIENTATION:
+                slot->mInUse = true;
+                slot->mAbsMTOrientation = rawEvent->value;
+                break;
+            case ABS_MT_TRACKING_ID:
+                if (mUsingSlotsProtocol && rawEvent->value < 0) {
+                    slot->clearIfInUse();
+                } else {
+                    slot->mInUse = true;
+                    slot->mAbsMTTrackingId = rawEvent->value;
+                }
+                break;
+            case ABS_MT_PRESSURE:
+                slot->mInUse = true;
+                slot->mAbsMTPressure = rawEvent->value;
+                break;
+            case ABS_MT_TOOL_TYPE:
+                slot->mInUse = true;
+                slot->mAbsMTToolType = rawEvent->value;
+                slot->mHaveAbsMTToolType = true;
+                break;
+            }
+        }
+    } else if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_MT_REPORT) {
+        // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+        mCurrentSlot += 1;
+    }
+}
+
+
+// --- MultiTouchMotionAccumulator::Slot ---
+
+MultiTouchMotionAccumulator::Slot::Slot() {
+    clear();
+}
+
+void MultiTouchMotionAccumulator::Slot::clearIfInUse() {
+    if (mInUse) {
+        clear();
+    }
+}
+
+void MultiTouchMotionAccumulator::Slot::clear() {
+    mInUse = false;
+    mHaveAbsMTTouchMinor = false;
+    mHaveAbsMTWidthMinor = false;
+    mHaveAbsMTToolType = false;
+    mAbsMTPositionX = 0;
+    mAbsMTPositionY = 0;
+    mAbsMTTouchMajor = 0;
+    mAbsMTTouchMinor = 0;
+    mAbsMTWidthMajor = 0;
+    mAbsMTWidthMinor = 0;
+    mAbsMTOrientation = 0;
+    mAbsMTTrackingId = -1;
+    mAbsMTPressure = 0;
+    mAbsMTToolType = 0;
+    mAbsMTDistance = 0;
+}
+
+int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+    if (mHaveAbsMTToolType) {
+        switch (mAbsMTToolType) {
+        case MT_TOOL_FINGER:
+            return AMOTION_EVENT_TOOL_TYPE_FINGER;
+        case MT_TOOL_PEN:
+            return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        }
+    }
+    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+
 // --- InputMapper ---
 
 InputMapper::InputMapper(InputDevice* device) :
@@ -1399,10 +1747,10 @@
     }
     info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f);
 
-    if (mHaveVWheel) {
+    if (mCursorMotionAccumulator.haveRelativeVWheel()) {
         info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f);
     }
-    if (mHaveHWheel) {
+    if (mCursorMotionAccumulator.haveRelativeHWheel()) {
         info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f);
     }
 }
@@ -1416,8 +1764,10 @@
         dump.appendFormat(INDENT3 "YScale: %0.3f\n", mYScale);
         dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
         dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
-        dump.appendFormat(INDENT3 "HaveVWheel: %s\n", toString(mHaveVWheel));
-        dump.appendFormat(INDENT3 "HaveHWheel: %s\n", toString(mHaveHWheel));
+        dump.appendFormat(INDENT3 "HaveVWheel: %s\n",
+                toString(mCursorMotionAccumulator.haveRelativeVWheel()));
+        dump.appendFormat(INDENT3 "HaveHWheel: %s\n",
+                toString(mCursorMotionAccumulator.haveRelativeHWheel()));
         dump.appendFormat(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
         dump.appendFormat(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
         dump.appendFormat(INDENT3 "ButtonState: 0x%08x\n", mLocked.buttonState);
@@ -1430,6 +1780,8 @@
     InputMapper::configure(config, changes);
 
     if (!changes) { // first time only
+        mCursorMotionAccumulator.configure(getDevice());
+
         // Configure basic parameters.
         configureParameters();
 
@@ -1454,9 +1806,6 @@
 
         mVWheelScale = 1.0f;
         mHWheelScale = 1.0f;
-
-        mHaveVWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_WHEEL);
-        mHaveHWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_HWHEEL);
     }
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
@@ -1506,7 +1855,8 @@
 }
 
 void CursorInputMapper::initializeLocked() {
-    mAccumulator.clear();
+    mCursorButtonAccumulator.clearButtons();
+    mCursorMotionAccumulator.clearRelativeAxes();
 
     mLocked.buttonState = 0;
     mLocked.downTime = 0;
@@ -1532,10 +1882,8 @@
 
         // Synthesize button up event on reset.
         nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
-        mAccumulator.clear();
-        mAccumulator.buttonDown = 0;
-        mAccumulator.buttonUp = buttonState;
-        mAccumulator.fields = Accumulator::FIELD_BUTTONS;
+        mCursorButtonAccumulator.clearButtons();
+        mCursorMotionAccumulator.clearRelativeAxes();
         sync(when);
     }
 
@@ -1543,64 +1891,15 @@
 }
 
 void CursorInputMapper::process(const RawEvent* rawEvent) {
-    switch (rawEvent->type) {
-    case EV_KEY: {
-        int32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode);
-        if (buttonState) {
-            if (rawEvent->value) {
-                mAccumulator.buttonDown = buttonState;
-                mAccumulator.buttonUp = 0;
-            } else {
-                mAccumulator.buttonDown = 0;
-                mAccumulator.buttonUp = buttonState;
-            }
-            mAccumulator.fields |= Accumulator::FIELD_BUTTONS;
+    mCursorButtonAccumulator.process(rawEvent);
+    mCursorMotionAccumulator.process(rawEvent);
 
-            // Sync now since BTN_MOUSE is not necessarily followed by SYN_REPORT and
-            // we need to ensure that we report the up/down promptly.
-            sync(rawEvent->when);
-            break;
-        }
-        break;
-    }
-
-    case EV_REL:
-        switch (rawEvent->scanCode) {
-        case REL_X:
-            mAccumulator.fields |= Accumulator::FIELD_REL_X;
-            mAccumulator.relX = rawEvent->value;
-            break;
-        case REL_Y:
-            mAccumulator.fields |= Accumulator::FIELD_REL_Y;
-            mAccumulator.relY = rawEvent->value;
-            break;
-        case REL_WHEEL:
-            mAccumulator.fields |= Accumulator::FIELD_REL_WHEEL;
-            mAccumulator.relWheel = rawEvent->value;
-            break;
-        case REL_HWHEEL:
-            mAccumulator.fields |= Accumulator::FIELD_REL_HWHEEL;
-            mAccumulator.relHWheel = rawEvent->value;
-            break;
-        }
-        break;
-
-    case EV_SYN:
-        switch (rawEvent->scanCode) {
-        case SYN_REPORT:
-            sync(rawEvent->when);
-            break;
-        }
-        break;
+    if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+        sync(rawEvent->when);
     }
 }
 
 void CursorInputMapper::sync(nsecs_t when) {
-    uint32_t fields = mAccumulator.fields;
-    if (fields == 0) {
-        return; // no new state changes, so nothing to do
-    }
-
     int32_t motionEventAction;
     int32_t lastButtonState, currentButtonState;
     PointerProperties pointerProperties;
@@ -1611,34 +1910,21 @@
         AutoMutex _l(mLock);
 
         lastButtonState = mLocked.buttonState;
+        currentButtonState = mCursorButtonAccumulator.getButtonState();
+        mLocked.buttonState = currentButtonState;
 
-        bool down, downChanged;
-        bool wasDown = isPointerDown(mLocked.buttonState);
-        bool buttonsChanged = fields & Accumulator::FIELD_BUTTONS;
-        if (buttonsChanged) {
-            mLocked.buttonState = (mLocked.buttonState | mAccumulator.buttonDown)
-                    & ~mAccumulator.buttonUp;
-
-            down = isPointerDown(mLocked.buttonState);
-
-            if (!wasDown && down) {
-                mLocked.downTime = when;
-                downChanged = true;
-            } else if (wasDown && !down) {
-                downChanged = true;
-            } else {
-                downChanged = false;
-            }
+        bool wasDown = isPointerDown(lastButtonState);
+        bool down = isPointerDown(currentButtonState);
+        bool downChanged;
+        if (!wasDown && down) {
+            mLocked.downTime = when;
+            downChanged = true;
+        } else if (wasDown && !down) {
+            downChanged = true;
         } else {
-            down = wasDown;
             downChanged = false;
         }
-
-        currentButtonState = mLocked.buttonState;
-
         downTime = mLocked.downTime;
-        float deltaX = fields & Accumulator::FIELD_REL_X ? mAccumulator.relX * mXScale : 0.0f;
-        float deltaY = fields & Accumulator::FIELD_REL_Y ? mAccumulator.relY * mYScale : 0.0f;
 
         if (downChanged) {
             motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
@@ -1648,6 +1934,9 @@
             motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
         }
 
+        float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
+        float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
+
         if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0
                 && (deltaX != 0.0f || deltaY != 0.0f)) {
             // Rotate motion based on display orientation if needed.
@@ -1667,25 +1956,17 @@
 
         pointerCoords.clear();
 
-        if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
-            vscroll = mAccumulator.relWheel;
-        } else {
-            vscroll = 0;
-        }
-        mWheelYVelocityControl.move(when, NULL, &vscroll);
+        vscroll = mCursorMotionAccumulator.getRelativeVWheel();
+        hscroll = mCursorMotionAccumulator.getRelativeHWheel();
 
-        if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
-            hscroll = mAccumulator.relHWheel;
-        } else {
-            hscroll = 0;
-        }
+        mWheelYVelocityControl.move(when, NULL, &vscroll);
         mWheelXVelocityControl.move(when, &hscroll, NULL);
 
         mPointerVelocityControl.move(when, &deltaX, &deltaY);
 
         if (mPointerController != NULL) {
             if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0
-                    || buttonsChanged) {
+                    || currentButtonState != lastButtonState) {
                 mPointerController->setPresentation(
                         PointerControllerInterface::PRESENTATION_POINTER);
 
@@ -1693,8 +1974,8 @@
                     mPointerController->move(deltaX, deltaY);
                 }
 
-                if (buttonsChanged) {
-                    mPointerController->setButtonState(mLocked.buttonState);
+                if (currentButtonState != lastButtonState) {
+                    mPointerController->setButtonState(currentButtonState);
                 }
 
                 mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
@@ -1755,7 +2036,7 @@
     synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
             policyFlags, lastButtonState, currentButtonState);
 
-    mAccumulator.clear();
+    mCursorMotionAccumulator.clearRelativeAxes();
 }
 
 int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
@@ -1881,10 +2162,11 @@
             const PointerData& pointer = mLastTouch.pointers[i];
             dump.appendFormat(INDENT5 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
                     "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
-                    "orientation=%d, distance=%d, isStylus=%s\n", i,
+                    "orientation=%d, distance=%d, toolType=%d, isHovering=%s\n", i,
                     pointer.id, pointer.x, pointer.y, pointer.pressure,
                     pointer.touchMajor, pointer.touchMinor, pointer.toolMajor, pointer.toolMinor,
-                    pointer.orientation, pointer.distance, toString(pointer.isStylus));
+                    pointer.orientation, pointer.distance,
+                    pointer.toolType, toString(pointer.isHovering));
         }
 
         if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
@@ -2924,6 +3206,7 @@
         calculatePointerIds();
     }
 
+    // Handle initial down events.
     uint32_t policyFlags = 0;
     if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
         if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) {
@@ -3420,7 +3703,7 @@
         PointerProperties& properties = mCurrentTouchProperties[i];
         properties.clear();
         properties.id = mCurrentTouch.pointers[i].id;
-        properties.toolType = getTouchToolType(mCurrentTouch.pointers[i].isStylus);
+        properties.toolType = mCurrentTouch.pointers[i].toolType;
     }
 
     *outXPrecision = mLocked.orientedXPrecision;
@@ -3604,7 +3887,7 @@
         PointerProperties pointerProperties;
         pointerProperties.clear();
         pointerProperties.id = 0;
-        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
 
         PointerCoords pointerCoords;
         pointerCoords.clear();
@@ -3838,8 +4121,7 @@
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
         mPointerGesture.currentGestureProperties[0].clear();
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
-        mPointerGesture.currentGestureProperties[0].toolType =
-                AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
         mPointerGesture.currentGestureCoords[0].clear();
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
@@ -3880,7 +4162,7 @@
                     mPointerGesture.currentGestureProperties[0].id =
                             mPointerGesture.activeGestureId;
                     mPointerGesture.currentGestureProperties[0].toolType =
-                            AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+                            AMOTION_EVENT_TOOL_TYPE_FINGER;
                     mPointerGesture.currentGestureCoords[0].clear();
                     mPointerGesture.currentGestureCoords[0].setAxisValue(
                             AMOTION_EVENT_AXIS_X, mPointerGesture.tapX);
@@ -3993,7 +4275,7 @@
         mPointerGesture.currentGestureProperties[0].clear();
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
         mPointerGesture.currentGestureProperties[0].toolType =
-                AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+                AMOTION_EVENT_TOOL_TYPE_FINGER;
         mPointerGesture.currentGestureCoords[0].clear();
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
@@ -4240,7 +4522,7 @@
             mPointerGesture.currentGestureProperties[0].clear();
             mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
             mPointerGesture.currentGestureProperties[0].toolType =
-                    AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+                    AMOTION_EVENT_TOOL_TYPE_FINGER;
             mPointerGesture.currentGestureCoords[0].clear();
             mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
                     mPointerGesture.referenceGestureX);
@@ -4331,7 +4613,7 @@
                 mPointerGesture.currentGestureProperties[i].clear();
                 mPointerGesture.currentGestureProperties[i].id = gestureId;
                 mPointerGesture.currentGestureProperties[i].toolType =
-                        AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+                        AMOTION_EVENT_TOOL_TYPE_FINGER;
                 mPointerGesture.currentGestureCoords[i].clear();
                 mPointerGesture.currentGestureCoords[i].setAxisValue(
                         AMOTION_EVENT_AXIS_X, mPointerGesture.referenceGestureX + deltaX);
@@ -4473,15 +4755,6 @@
     } // release lock
 }
 
-int32_t TouchInputMapper::getTouchToolType(bool isStylus) const {
-    if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN) {
-        return isStylus ? AMOTION_EVENT_TOOL_TYPE_STYLUS : AMOTION_EVENT_TOOL_TYPE_FINGER;
-    } else {
-        return isStylus ? AMOTION_EVENT_TOOL_TYPE_INDIRECT_STYLUS
-                : AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
-    }
-}
-
 bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) {
     return x >= mRawAxes.x.minValue && x <= mRawAxes.x.maxValue
             && y >= mRawAxes.y.minValue && y <= mRawAxes.y.maxValue;
@@ -4760,14 +5033,9 @@
 }
 
 void SingleTouchInputMapper::clearState() {
-    mAccumulator.clear();
-
-    mDown = false;
-    mX = 0;
-    mY = 0;
-    mPressure = 0; // default to 0 for devices that don't report pressure
-    mToolWidth = 0; // default to 0 for devices that don't report tool width
-    mButtonState = 0;
+    mCursorButtonAccumulator.clearButtons();
+    mTouchButtonAccumulator.clearButtons();
+    mSingleTouchMotionAccumulator.clearAbsoluteAxes();
 }
 
 void SingleTouchInputMapper::reset() {
@@ -4777,144 +5045,79 @@
  }
 
 void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
-    switch (rawEvent->type) {
-    case EV_KEY:
-        switch (rawEvent->scanCode) {
-        case BTN_TOUCH:
-            mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH;
-            mAccumulator.btnTouch = rawEvent->value != 0;
-            // Don't sync immediately.  Wait until the next SYN_REPORT since we might
-            // not have received valid position information yet.  This logic assumes that
-            // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet.
-            break;
-        default:
-            if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
-                int32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode);
-                if (buttonState) {
-                    if (rawEvent->value) {
-                        mAccumulator.buttonDown |= buttonState;
-                    } else {
-                        mAccumulator.buttonUp |= buttonState;
-                    }
-                    mAccumulator.fields |= Accumulator::FIELD_BUTTONS;
-                }
-            }
-            break;
-        }
-        break;
+    mCursorButtonAccumulator.process(rawEvent);
+    mTouchButtonAccumulator.process(rawEvent);
+    mSingleTouchMotionAccumulator.process(rawEvent);
 
-    case EV_ABS:
-        switch (rawEvent->scanCode) {
-        case ABS_X:
-            mAccumulator.fields |= Accumulator::FIELD_ABS_X;
-            mAccumulator.absX = rawEvent->value;
-            break;
-        case ABS_Y:
-            mAccumulator.fields |= Accumulator::FIELD_ABS_Y;
-            mAccumulator.absY = rawEvent->value;
-            break;
-        case ABS_PRESSURE:
-            mAccumulator.fields |= Accumulator::FIELD_ABS_PRESSURE;
-            mAccumulator.absPressure = rawEvent->value;
-            break;
-        case ABS_TOOL_WIDTH:
-            mAccumulator.fields |= Accumulator::FIELD_ABS_TOOL_WIDTH;
-            mAccumulator.absToolWidth = rawEvent->value;
-            break;
-        }
-        break;
-
-    case EV_SYN:
-        switch (rawEvent->scanCode) {
-        case SYN_REPORT:
-            sync(rawEvent->when);
-            break;
-        }
-        break;
+    if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+        sync(rawEvent->when);
     }
 }
 
 void SingleTouchInputMapper::sync(nsecs_t when) {
-    uint32_t fields = mAccumulator.fields;
-    if (fields == 0) {
-        return; // no new state changes, so nothing to do
-    }
-
-    if (fields & Accumulator::FIELD_BTN_TOUCH) {
-        mDown = mAccumulator.btnTouch;
-    }
-
-    if (fields & Accumulator::FIELD_ABS_X) {
-        mX = mAccumulator.absX;
-    }
-
-    if (fields & Accumulator::FIELD_ABS_Y) {
-        mY = mAccumulator.absY;
-    }
-
-    if (fields & Accumulator::FIELD_ABS_PRESSURE) {
-        mPressure = mAccumulator.absPressure;
-    }
-
-    if (fields & Accumulator::FIELD_ABS_TOOL_WIDTH) {
-        mToolWidth = mAccumulator.absToolWidth;
-    }
-
-    if (fields & Accumulator::FIELD_BUTTONS) {
-        mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp;
-    }
-
     mCurrentTouch.clear();
 
-    if (mDown) {
+    if (mTouchButtonAccumulator.isActive()) {
+        uint32_t buttonState = mTouchButtonAccumulator.getButtonState();
+        bool isHovering = mTouchButtonAccumulator.isHovering();
+        if (mSingleTouchMotionAccumulator.getAbsoluteDistance() > 0) {
+            isHovering = true;
+        }
+
         mCurrentTouch.pointerCount = 1;
-        mCurrentTouch.pointers[0].id = 0;
-        mCurrentTouch.pointers[0].x = mX;
-        mCurrentTouch.pointers[0].y = mY;
-        mCurrentTouch.pointers[0].pressure = mPressure;
-        mCurrentTouch.pointers[0].touchMajor = 0;
-        mCurrentTouch.pointers[0].touchMinor = 0;
-        mCurrentTouch.pointers[0].toolMajor = mToolWidth;
-        mCurrentTouch.pointers[0].toolMinor = mToolWidth;
-        mCurrentTouch.pointers[0].orientation = 0;
-        mCurrentTouch.pointers[0].distance = 0;
-        mCurrentTouch.pointers[0].isStylus = false; // TODO: Set stylus
         mCurrentTouch.idToIndex[0] = 0;
         mCurrentTouch.idBits.markBit(0);
-        mCurrentTouch.buttonState = mButtonState;
+        mCurrentTouch.buttonState = buttonState;
+
+        PointerData& outPointer = mCurrentTouch.pointers[0];
+        outPointer.id = 0;
+        outPointer.x = mSingleTouchMotionAccumulator.getAbsoluteX();
+        outPointer.y = mSingleTouchMotionAccumulator.getAbsoluteY();
+        outPointer.pressure = mSingleTouchMotionAccumulator.getAbsolutePressure();
+        outPointer.touchMajor = 0;
+        outPointer.touchMinor = 0;
+        outPointer.toolMajor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+        outPointer.toolMinor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+        outPointer.orientation = 0;
+        outPointer.distance = mSingleTouchMotionAccumulator.getAbsoluteDistance();
+        outPointer.toolType = mTouchButtonAccumulator.getToolType();
+        if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+            outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        }
+        outPointer.isHovering = isHovering;
     }
 
     syncTouch(when, true);
-
-    mAccumulator.clear();
 }
 
 void SingleTouchInputMapper::configureRawAxes() {
     TouchInputMapper::configureRawAxes();
 
+    mTouchButtonAccumulator.configure(getDevice());
+
     getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mRawAxes.x);
     getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mRawAxes.y);
     getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mRawAxes.pressure);
     getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mRawAxes.toolMajor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_DISTANCE, & mRawAxes.distance);
 }
 
 
 // --- MultiTouchInputMapper ---
 
 MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
-        TouchInputMapper(device), mSlotCount(0), mUsingSlotsProtocol(false) {
+        TouchInputMapper(device) {
 }
 
 MultiTouchInputMapper::~MultiTouchInputMapper() {
 }
 
 void MultiTouchInputMapper::clearState() {
-    mAccumulator.clearSlots(mSlotCount);
-    mAccumulator.clearButtons();
-    mButtonState = 0;
+    mCursorButtonAccumulator.clearButtons();
+    mTouchButtonAccumulator.clearButtons();
     mPointerIdBits.clear();
 
-    if (mUsingSlotsProtocol) {
+    if (mMultiTouchMotionAccumulator.isUsingSlotsProtocol()) {
         // Query the driver for the current slot index and use it as the initial slot
         // before we start reading events from the device.  It is possible that the
         // current slot index will not be the same as it was when the first event was
@@ -4924,12 +5127,16 @@
         // two slots will be confused until the next ABS_MT_SLOT event is received.
         // This can cause the touch point to "jump", but at least there will be
         // no stuck touches.
+        int32_t initialSlot;
         status_t status = getEventHub()->getAbsoluteAxisValue(getDeviceId(), ABS_MT_SLOT,
-                &mAccumulator.currentSlot);
+                &initialSlot);
         if (status) {
             LOGW("Could not retrieve current multitouch slot index.  status=%d", status);
-            mAccumulator.currentSlot = -1;
+            initialSlot = -1;
         }
+        mMultiTouchMotionAccumulator.clearSlots(initialSlot);
+    } else {
+        mMultiTouchMotionAccumulator.clearSlots(-1);
     }
 }
 
@@ -4940,124 +5147,26 @@
 }
 
 void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
-    switch (rawEvent->type) {
-    case EV_KEY: {
-        if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
-            int32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode);
-            if (buttonState) {
-                if (rawEvent->value) {
-                    mAccumulator.buttonDown |= buttonState;
-                } else {
-                    mAccumulator.buttonUp |= buttonState;
-                }
-            }
-        }
-        break;
-    }
+    mCursorButtonAccumulator.process(rawEvent);
+    mTouchButtonAccumulator.process(rawEvent);
+    mMultiTouchMotionAccumulator.process(rawEvent);
 
-    case EV_ABS: {
-        bool newSlot = false;
-        if (mUsingSlotsProtocol && rawEvent->scanCode == ABS_MT_SLOT) {
-            mAccumulator.currentSlot = rawEvent->value;
-            newSlot = true;
-        }
-
-        if (mAccumulator.currentSlot < 0 || size_t(mAccumulator.currentSlot) >= mSlotCount) {
-#if DEBUG_POINTERS
-            if (newSlot) {
-                LOGW("MultiTouch device %s emitted invalid slot index %d but it "
-                        "should be between 0 and %d; ignoring this slot.",
-                        getDeviceName().string(), mAccumulator.currentSlot, mSlotCount);
-            }
-#endif
-            break;
-        }
-
-        Accumulator::Slot* slot = &mAccumulator.slots[mAccumulator.currentSlot];
-
-        switch (rawEvent->scanCode) {
-        case ABS_MT_POSITION_X:
-            slot->fields |= Accumulator::FIELD_ABS_MT_POSITION_X;
-            slot->absMTPositionX = rawEvent->value;
-            break;
-        case ABS_MT_POSITION_Y:
-            slot->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y;
-            slot->absMTPositionY = rawEvent->value;
-            break;
-        case ABS_MT_TOUCH_MAJOR:
-            slot->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
-            slot->absMTTouchMajor = rawEvent->value;
-            break;
-        case ABS_MT_TOUCH_MINOR:
-            slot->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR;
-            slot->absMTTouchMinor = rawEvent->value;
-            break;
-        case ABS_MT_WIDTH_MAJOR:
-            slot->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
-            slot->absMTWidthMajor = rawEvent->value;
-            break;
-        case ABS_MT_WIDTH_MINOR:
-            slot->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR;
-            slot->absMTWidthMinor = rawEvent->value;
-            break;
-        case ABS_MT_ORIENTATION:
-            slot->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION;
-            slot->absMTOrientation = rawEvent->value;
-            break;
-        case ABS_MT_TRACKING_ID:
-            if (mUsingSlotsProtocol && rawEvent->value < 0) {
-                slot->clear();
-            } else {
-                slot->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID;
-                slot->absMTTrackingId = rawEvent->value;
-            }
-            break;
-        case ABS_MT_PRESSURE:
-            slot->fields |= Accumulator::FIELD_ABS_MT_PRESSURE;
-            slot->absMTPressure = rawEvent->value;
-            break;
-        case ABS_MT_TOOL_TYPE:
-            slot->fields |= Accumulator::FIELD_ABS_MT_TOOL_TYPE;
-            slot->absMTToolType = rawEvent->value;
-            break;
-        }
-        break;
-    }
-
-    case EV_SYN:
-        switch (rawEvent->scanCode) {
-        case SYN_MT_REPORT: {
-            // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
-            mAccumulator.currentSlot += 1;
-            break;
-        }
-
-        case SYN_REPORT:
-            sync(rawEvent->when);
-            break;
-        }
-        break;
+    if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+        sync(rawEvent->when);
     }
 }
 
 void MultiTouchInputMapper::sync(nsecs_t when) {
-    static const uint32_t REQUIRED_FIELDS =
-            Accumulator::FIELD_ABS_MT_POSITION_X | Accumulator::FIELD_ABS_MT_POSITION_Y;
-
-    size_t inCount = mSlotCount;
+    size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
     size_t outCount = 0;
     bool havePointerIds = true;
 
     mCurrentTouch.clear();
 
     for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
-        const Accumulator::Slot& inSlot = mAccumulator.slots[inIndex];
-        uint32_t fields = inSlot.fields;
-
-        if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
-            // Some drivers send empty MT sync packets without X / Y to indicate a pointer up.
-            // This may also indicate an unused slot.
-            // Drop this finger.
+        const MultiTouchMotionAccumulator::Slot* inSlot =
+                mMultiTouchMotionAccumulator.getSlot(inIndex);
+        if (!inSlot->isInUse()) {
             continue;
         }
 
@@ -5071,71 +5180,32 @@
         }
 
         PointerData& outPointer = mCurrentTouch.pointers[outCount];
-        outPointer.x = inSlot.absMTPositionX;
-        outPointer.y = inSlot.absMTPositionY;
+        outPointer.x = inSlot->getX();
+        outPointer.y = inSlot->getY();
+        outPointer.pressure = inSlot->getPressure();
+        outPointer.touchMajor = inSlot->getTouchMajor();
+        outPointer.touchMinor = inSlot->getTouchMinor();
+        outPointer.toolMajor = inSlot->getToolMajor();
+        outPointer.toolMinor = inSlot->getToolMinor();
+        outPointer.orientation = inSlot->getOrientation();
+        outPointer.distance = inSlot->getDistance();
 
-        if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
-            outPointer.pressure = inSlot.absMTPressure;
-        } else {
-            // Default pressure to 0 if absent.
-            outPointer.pressure = 0;
+        outPointer.toolType = inSlot->getToolType();
+        if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+            outPointer.toolType = mTouchButtonAccumulator.getToolType();
+            if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+                outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            }
         }
 
-        if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MAJOR) {
-            outPointer.touchMajor = inSlot.absMTTouchMajor;
-        } else {
-            // Default touch area to 0 if absent.
-            outPointer.touchMajor = 0;
-        }
-
-        if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) {
-            outPointer.touchMinor = inSlot.absMTTouchMinor;
-        } else {
-            // Assume touch area is circular.
-            outPointer.touchMinor = outPointer.touchMajor;
-        }
-
-        if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MAJOR) {
-            outPointer.toolMajor = inSlot.absMTWidthMajor;
-        } else {
-            // Default tool area to 0 if absent.
-            outPointer.toolMajor = 0;
-        }
-
-        if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) {
-            outPointer.toolMinor = inSlot.absMTWidthMinor;
-        } else {
-            // Assume tool area is circular.
-            outPointer.toolMinor = outPointer.toolMajor;
-        }
-
-        if (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) {
-            outPointer.orientation = inSlot.absMTOrientation;
-        } else {
-            // Default orientation to vertical if absent.
-            outPointer.orientation = 0;
-        }
-
-        if (fields & Accumulator::FIELD_ABS_MT_DISTANCE) {
-            outPointer.distance = inSlot.absMTDistance;
-        } else {
-            // Default distance is 0 (direct contact).
-            outPointer.distance = 0;
-        }
-
-        if (fields & Accumulator::FIELD_ABS_MT_TOOL_TYPE) {
-            outPointer.isStylus = (inSlot.absMTToolType == MT_TOOL_PEN);
-        } else {
-            // Assume this is not a stylus.
-            outPointer.isStylus = false;
-        }
+        outPointer.isHovering = mTouchButtonAccumulator.isHovering()
+                || inSlot->getDistance() > 0;
 
         // Assign pointer id using tracking id if available.
         if (havePointerIds) {
+            int32_t trackingId = inSlot->getTrackingId();
             int32_t id = -1;
-            if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) {
-                int32_t trackingId = inSlot.absMTTrackingId;
-
+            if (trackingId >= 0) {
                 for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty(); ) {
                     uint32_t n = idBits.firstMarkedBit();
                     idBits.clearBit(n);
@@ -5165,23 +5235,22 @@
     }
 
     mCurrentTouch.pointerCount = outCount;
-
-    mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp;
-    mCurrentTouch.buttonState = mButtonState;
+    mCurrentTouch.buttonState = mTouchButtonAccumulator.getButtonState();
 
     mPointerIdBits = mCurrentTouch.idBits;
 
     syncTouch(when, havePointerIds);
 
-    if (!mUsingSlotsProtocol) {
-        mAccumulator.clearSlots(mSlotCount);
+    if (!mMultiTouchMotionAccumulator.isUsingSlotsProtocol()) {
+        mMultiTouchMotionAccumulator.clearSlots(-1);
     }
-    mAccumulator.clearButtons();
 }
 
 void MultiTouchInputMapper::configureRawAxes() {
     TouchInputMapper::configureRawAxes();
 
+    mTouchButtonAccumulator.configure(getDevice());
+
     getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, &mRawAxes.x);
     getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, &mRawAxes.y);
     getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, &mRawAxes.touchMajor);
@@ -5196,21 +5265,18 @@
 
     if (mRawAxes.trackingId.valid
             && mRawAxes.slot.valid && mRawAxes.slot.minValue == 0 && mRawAxes.slot.maxValue > 0) {
-        mSlotCount = mRawAxes.slot.maxValue + 1;
-        if (mSlotCount > MAX_SLOTS) {
+        size_t slotCount = mRawAxes.slot.maxValue + 1;
+        if (slotCount > MAX_SLOTS) {
             LOGW("MultiTouch Device %s reported %d slots but the framework "
                     "only supports a maximum of %d slots at this time.",
-                    getDeviceName().string(), mSlotCount, MAX_SLOTS);
-            mSlotCount = MAX_SLOTS;
+                    getDeviceName().string(), slotCount, MAX_SLOTS);
+            slotCount = MAX_SLOTS;
         }
-        mUsingSlotsProtocol = true;
+        mMultiTouchMotionAccumulator.configure(slotCount, true /*usingSlotsProtocol*/);
     } else {
-        mSlotCount = MAX_POINTERS;
-        mUsingSlotsProtocol = false;
+        mMultiTouchMotionAccumulator.configure(MAX_POINTERS, false /*usingSlotsProtocol*/);
     }
 
-    mAccumulator.allocateSlots(mSlotCount);
-
     clearState();
 }
 
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index b1fdcf2..f9750d0 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -430,9 +430,8 @@
 
     void fadePointer();
 
-    inline const PropertyMap& getConfiguration() {
-        return mConfiguration;
-    }
+    inline const PropertyMap& getConfiguration() { return mConfiguration; }
+    inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
 
 private:
     InputReaderContext* mContext;
@@ -452,6 +451,171 @@
 };
 
 
+/* Keeps track of the state of mouse or touch pad buttons. */
+class CursorButtonAccumulator {
+public:
+    CursorButtonAccumulator();
+
+    void clearButtons();
+    void process(const RawEvent* rawEvent);
+
+    uint32_t getButtonState() const;
+
+private:
+    bool mBtnLeft;
+    bool mBtnRight;
+    bool mBtnMiddle;
+    bool mBtnBack;
+    bool mBtnSide;
+    bool mBtnForward;
+    bool mBtnExtra;
+    bool mBtnTask;
+};
+
+
+/* Keeps track of cursor movements. */
+
+class CursorMotionAccumulator {
+public:
+    CursorMotionAccumulator();
+    void configure(InputDevice* device);
+
+    void clearRelativeAxes();
+    void process(const RawEvent* rawEvent);
+
+    inline bool haveRelativeVWheel() const { return mHaveRelWheel; }
+    inline bool haveRelativeHWheel() const { return mHaveRelHWheel; }
+
+    inline int32_t getRelativeX() const { return mRelX; }
+    inline int32_t getRelativeY() const { return mRelY; }
+    inline int32_t getRelativeVWheel() const { return mRelWheel; }
+    inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+
+private:
+    bool mHaveRelWheel;
+    bool mHaveRelHWheel;
+
+    int32_t mRelX;
+    int32_t mRelY;
+    int32_t mRelWheel;
+    int32_t mRelHWheel;
+};
+
+
+/* Keeps track of the state of touch, stylus and tool buttons. */
+class TouchButtonAccumulator {
+public:
+    TouchButtonAccumulator();
+    void configure(InputDevice* device);
+
+    void clearButtons();
+    void process(const RawEvent* rawEvent);
+
+    uint32_t getButtonState() const;
+    int32_t getToolType() const;
+    bool isActive() const;
+    bool isHovering() const;
+
+private:
+    bool mHaveBtnTouch;
+
+    bool mBtnTouch;
+    bool mBtnStylus;
+    bool mBtnStylus2;
+    bool mBtnToolFinger;
+    bool mBtnToolPen;
+    bool mBtnToolRubber;
+};
+
+
+/* Keeps track of the state of single-touch protocol. */
+class SingleTouchMotionAccumulator {
+public:
+    SingleTouchMotionAccumulator();
+
+    void clearAbsoluteAxes();
+    void process(const RawEvent* rawEvent);
+
+    inline int32_t getAbsoluteX() const { return mAbsX; }
+    inline int32_t getAbsoluteY() const { return mAbsY; }
+    inline int32_t getAbsolutePressure() const { return mAbsPressure; }
+    inline int32_t getAbsoluteToolWidth() const { return mAbsToolWidth; }
+    inline int32_t getAbsoluteDistance() const { return mAbsDistance; }
+
+private:
+    int32_t mAbsX;
+    int32_t mAbsY;
+    int32_t mAbsPressure;
+    int32_t mAbsToolWidth;
+    int32_t mAbsDistance;
+};
+
+
+/* Keeps track of the state of multi-touch protocol. */
+class MultiTouchMotionAccumulator {
+public:
+    class Slot {
+    public:
+        inline bool isInUse() const { return mInUse; }
+        inline int32_t getX() const { return mAbsMTPositionX; }
+        inline int32_t getY() const { return mAbsMTPositionY; }
+        inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; }
+        inline int32_t getTouchMinor() const {
+            return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; }
+        inline int32_t getToolMajor() const { return mAbsMTWidthMajor; }
+        inline int32_t getToolMinor() const {
+            return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; }
+        inline int32_t getOrientation() const { return mAbsMTOrientation; }
+        inline int32_t getTrackingId() const { return mAbsMTTrackingId; }
+        inline int32_t getPressure() const { return mAbsMTPressure; }
+        inline int32_t getDistance() const { return mAbsMTDistance; }
+        inline int32_t getToolType() const;
+
+    private:
+        friend class MultiTouchMotionAccumulator;
+
+        bool mInUse;
+        bool mHaveAbsMTTouchMinor;
+        bool mHaveAbsMTWidthMinor;
+        bool mHaveAbsMTToolType;
+
+        int32_t mAbsMTPositionX;
+        int32_t mAbsMTPositionY;
+        int32_t mAbsMTTouchMajor;
+        int32_t mAbsMTTouchMinor;
+        int32_t mAbsMTWidthMajor;
+        int32_t mAbsMTWidthMinor;
+        int32_t mAbsMTOrientation;
+        int32_t mAbsMTTrackingId;
+        int32_t mAbsMTPressure;
+        int32_t mAbsMTToolType;
+        int32_t mAbsMTDistance;
+
+        Slot();
+        void clearIfInUse();
+        void clear();
+    };
+
+    MultiTouchMotionAccumulator();
+    ~MultiTouchMotionAccumulator();
+
+    void configure(size_t slotCount, bool usingSlotsProtocol);
+    void process(const RawEvent* rawEvent);
+
+    void clearSlots(int32_t initialSlot);
+
+    inline bool isUsingSlotsProtocol() const { return mUsingSlotsProtocol; }
+    inline size_t getSlotCount() const { return mSlotCount; }
+    inline const Slot* getSlot(size_t index) const { return &mSlots[index]; }
+
+private:
+    int32_t mCurrentSlot;
+    Slot* mSlots;
+    size_t mSlotCount;
+    bool mUsingSlotsProtocol;
+};
+
+
 /* An input mapper transforms raw input events into cooked event data.
  * A single input device can have multiple associated input mappers in order to interpret
  * different classes of events.
@@ -615,29 +779,8 @@
         bool orientationAware;
     } mParameters;
 
-    struct Accumulator {
-        enum {
-            FIELD_BUTTONS = 1,
-            FIELD_REL_X = 2,
-            FIELD_REL_Y = 4,
-            FIELD_REL_WHEEL = 8,
-            FIELD_REL_HWHEEL = 16,
-        };
-
-        uint32_t fields;
-
-        uint32_t buttonDown;
-        uint32_t buttonUp;
-
-        int32_t relX;
-        int32_t relY;
-        int32_t relWheel;
-        int32_t relHWheel;
-
-        inline void clear() {
-            fields = 0;
-        }
-    } mAccumulator;
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    CursorMotionAccumulator mCursorMotionAccumulator;
 
     int32_t mSource;
     float mXScale;
@@ -645,8 +788,6 @@
     float mXPrecision;
     float mYPrecision;
 
-    bool mHaveVWheel;
-    bool mHaveHWheel;
     float mVWheelScale;
     float mHWheelScale;
 
@@ -722,7 +863,8 @@
         int32_t toolMinor;
         int32_t orientation;
         int32_t distance;
-        bool isStylus;
+        int32_t toolType; // AMOTION_EVENT_TOOL_TYPE constant
+        bool isHovering;
 
         inline bool operator== (const PointerData& other) const {
             return id == other.id
@@ -734,7 +876,9 @@
                     && toolMajor == other.toolMajor
                     && toolMinor == other.toolMinor
                     && orientation == other.orientation
-                    && distance == other.distance;
+                    && distance == other.distance
+                    && toolType == other.toolType
+                    && isHovering == other.isHovering;
         }
         inline bool operator!= (const PointerData& other) const {
             return !(*this == other);
@@ -1201,7 +1345,6 @@
 
     void suppressSwipeOntoVirtualKeys(nsecs_t when);
 
-    int32_t getTouchToolType(bool isStylus) const;
     bool isPointInsideSurfaceLocked(int32_t x, int32_t y);
     const VirtualKey* findVirtualKeyHitLocked(int32_t x, int32_t y);
 
@@ -1221,40 +1364,9 @@
     virtual void configureRawAxes();
 
 private:
-    struct Accumulator {
-        enum {
-            FIELD_BTN_TOUCH = 1,
-            FIELD_ABS_X = 2,
-            FIELD_ABS_Y = 4,
-            FIELD_ABS_PRESSURE = 8,
-            FIELD_ABS_TOOL_WIDTH = 16,
-            FIELD_BUTTONS = 32,
-        };
-
-        uint32_t fields;
-
-        bool btnTouch;
-        int32_t absX;
-        int32_t absY;
-        int32_t absPressure;
-        int32_t absToolWidth;
-
-        uint32_t buttonDown;
-        uint32_t buttonUp;
-
-        inline void clear() {
-            fields = 0;
-            buttonDown = 0;
-            buttonUp = 0;
-        }
-    } mAccumulator;
-
-    bool mDown;
-    int32_t mX;
-    int32_t mY;
-    int32_t mPressure;
-    int32_t mToolWidth;
-    int32_t mButtonState;
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    TouchButtonAccumulator mTouchButtonAccumulator;
+    SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
 
     void clearState();
 
@@ -1274,83 +1386,9 @@
     virtual void configureRawAxes();
 
 private:
-    struct Accumulator {
-        enum {
-            FIELD_ABS_MT_POSITION_X = 1 << 0,
-            FIELD_ABS_MT_POSITION_Y = 1 << 1,
-            FIELD_ABS_MT_TOUCH_MAJOR = 1 << 2,
-            FIELD_ABS_MT_TOUCH_MINOR = 1 << 3,
-            FIELD_ABS_MT_WIDTH_MAJOR = 1 << 4,
-            FIELD_ABS_MT_WIDTH_MINOR = 1 << 5,
-            FIELD_ABS_MT_ORIENTATION = 1 << 6,
-            FIELD_ABS_MT_TRACKING_ID = 1 << 7,
-            FIELD_ABS_MT_PRESSURE = 1 << 8,
-            FIELD_ABS_MT_TOOL_TYPE = 1 << 9,
-            FIELD_ABS_MT_DISTANCE = 1 << 10,
-        };
-
-        struct Slot {
-            uint32_t fields; // 0 if slot is unused
-
-            int32_t absMTPositionX;
-            int32_t absMTPositionY;
-            int32_t absMTTouchMajor;
-            int32_t absMTTouchMinor;
-            int32_t absMTWidthMajor;
-            int32_t absMTWidthMinor;
-            int32_t absMTOrientation;
-            int32_t absMTTrackingId;
-            int32_t absMTPressure;
-            int32_t absMTToolType;
-            int32_t absMTDistance;
-
-            inline Slot() {
-                clear();
-            }
-
-            inline void clear() {
-                fields = 0;
-            }
-        };
-
-        // Current slot index.
-        int32_t currentSlot;
-
-        // Array of slots.
-        Slot* slots;
-
-        // Bitfield of buttons that went down or up.
-        uint32_t buttonDown;
-        uint32_t buttonUp;
-
-        Accumulator() : currentSlot(0), slots(NULL), buttonDown(0), buttonUp(0) {
-        }
-
-        ~Accumulator() {
-            delete[] slots;
-        }
-
-        void allocateSlots(size_t slotCount) {
-            slots = new Slot[slotCount];
-        }
-
-        void clearSlots(size_t slotCount) {
-            for (size_t i = 0; i < slotCount; i++) {
-                slots[i].clear();
-            }
-            currentSlot = 0;
-        }
-
-        void clearButtons() {
-            buttonDown = 0;
-            buttonUp = 0;
-        }
-    } mAccumulator;
-
-    size_t mSlotCount;
-    bool mUsingSlotsProtocol;
-
-    int32_t mButtonState;
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    TouchButtonAccumulator mTouchButtonAccumulator;
+    MultiTouchMotionAccumulator mMultiTouchMotionAccumulator;
 
     // Specifies the pointer id bits that are in use, and their associated tracking id.
     BitSet32 mPointerIdBits;
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 131894a..7a6af25 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -707,6 +707,15 @@
         return result;
     }
 
+    virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const {
+        Device* device = getDevice(deviceId);
+        if (device) {
+            ssize_t index = device->keys.indexOfKey(scanCode);
+            return index >= 0;
+        }
+        return false;
+    }
+
     virtual bool hasLed(int32_t deviceId, int32_t led) const {
         Device* device = getDevice(deviceId);
         return device && device->leds.indexOfKey(led) >= 0;
@@ -2116,6 +2125,7 @@
     // Button press.
     // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
     process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -2137,6 +2147,7 @@
 
     // Button release.  Should have same down time.
     process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -2190,6 +2201,7 @@
 
     // Button press without following sync.
     process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2197,6 +2209,7 @@
 
     // Button release without following sync.
     process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2233,6 +2246,7 @@
 
     // Release Button.
     process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2248,10 +2262,12 @@
 
     // Button press.
     process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
 
     // Button release.
     process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
 
     // Reset.  Should not synthesize button up since button is not pressed.
@@ -2269,6 +2285,7 @@
 
     // Button press.
     process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
 
     // Reset.  Should synthesize button up.
@@ -2445,6 +2462,7 @@
 
 class SingleTouchInputMapperTest : public TouchInputMapperTest {
 protected:
+    void prepareButtons();
     void prepareAxes(int axes);
 
     void processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
@@ -2455,6 +2473,10 @@
     void processSync(SingleTouchInputMapper* mapper);
 };
 
+void SingleTouchInputMapperTest::prepareButtons() {
+    mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, AKEYCODE_UNKNOWN, 0);
+}
+
 void SingleTouchInputMapperTest::prepareAxes(int axes) {
     if (axes & POSITION) {
         mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_X,
@@ -2504,6 +2526,7 @@
 
 TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) {
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareButtons();
     prepareAxes(POSITION);
     addMapperAndConfigure(mapper);
 
@@ -2514,6 +2537,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_X);
     mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_Y);
+    prepareButtons();
     prepareAxes(POSITION);
     addMapperAndConfigure(mapper);
 
@@ -2522,6 +2546,7 @@
 
 TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) {
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareButtons();
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchPad");
     addMapperAndConfigure(mapper);
@@ -2531,6 +2556,7 @@
 
 TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+    prepareButtons();
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addMapperAndConfigure(mapper);
@@ -2542,6 +2568,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2570,6 +2597,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2598,6 +2626,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2615,6 +2644,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2649,6 +2679,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2676,6 +2707,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2726,6 +2758,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2847,6 +2880,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -2920,6 +2954,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
     addMapperAndConfigure(mapper);
@@ -3009,6 +3044,7 @@
 TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_DoesNotRotateMotions) {
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
     prepareAxes(POSITION);
     addConfigurationProperty("touch.orientationAware", "0");
     addMapperAndConfigure(mapper);
@@ -3032,6 +3068,7 @@
 TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_RotatesMotions) {
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareButtons();
     prepareAxes(POSITION);
     addMapperAndConfigure(mapper);
 
@@ -3094,6 +3131,7 @@
     SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareButtons();
     prepareAxes(POSITION | PRESSURE | TOOL);
     addMapperAndConfigure(mapper);
 
@@ -3815,6 +3853,7 @@
     FakeInputDispatcher::NotifyMotionArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+
     ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
             args.action);