Configure device classes for evdev devices.

Change-Id: Ia75b71253771d9d558c59411e27f8a51e352fb8b
diff --git a/modules/input/evdev/InputDevice.cpp b/modules/input/evdev/InputDevice.cpp
index 16f8039..883d6d4 100644
--- a/modules/input/evdev/InputDevice.cpp
+++ b/modules/input/evdev/InputDevice.cpp
@@ -17,10 +17,14 @@
 #define LOG_TAG "InputDevice"
 #define LOG_NDEBUG 0
 
+// Enables debug output for processing input events
+#define DEBUG_INPUT_EVENTS 0
+
 #include <linux/input.h>
 
 #define __STDC_FORMAT_MACROS
 #include <cinttypes>
+#include <cstdlib>
 #include <string>
 
 #include <utils/Log.h>
@@ -34,18 +38,177 @@
 
 namespace android {
 
-EvdevDevice::EvdevDevice(const std::shared_ptr<InputDeviceNode>& node) :
-    mDeviceNode(node) {}
+static InputBus getInputBus(const std::shared_ptr<InputDeviceNode>& node) {
+    switch (node->getBusType()) {
+        case BUS_USB:
+            return INPUT_BUS_USB;
+        case BUS_BLUETOOTH:
+            return INPUT_BUS_BT;
+        case BUS_RS232:
+            return INPUT_BUS_SERIAL;
+        default:
+            // TODO: check for other linux bus types that might not be built-in
+            return INPUT_BUS_BUILTIN;
+    }
+}
+
+static uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) {
+    // Touch devices get dibs on touch-related axes.
+    if (deviceClasses & INPUT_DEVICE_CLASS_TOUCH) {
+        switch (axis) {
+            case ABS_X:
+            case ABS_Y:
+            case ABS_PRESSURE:
+            case ABS_TOOL_WIDTH:
+            case ABS_DISTANCE:
+            case ABS_TILT_X:
+            case ABS_TILT_Y:
+            case ABS_MT_SLOT:
+            case ABS_MT_TOUCH_MAJOR:
+            case ABS_MT_TOUCH_MINOR:
+            case ABS_MT_WIDTH_MAJOR:
+            case ABS_MT_WIDTH_MINOR:
+            case ABS_MT_ORIENTATION:
+            case ABS_MT_POSITION_X:
+            case ABS_MT_POSITION_Y:
+            case ABS_MT_TOOL_TYPE:
+            case ABS_MT_BLOB_ID:
+            case ABS_MT_TRACKING_ID:
+            case ABS_MT_PRESSURE:
+            case ABS_MT_DISTANCE:
+                return INPUT_DEVICE_CLASS_TOUCH;
+        }
+    }
+
+    // External stylus gets the pressure axis
+    if (deviceClasses & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS) {
+        if (axis == ABS_PRESSURE) {
+            return INPUT_DEVICE_CLASS_EXTERNAL_STYLUS;
+        }
+    }
+
+    // Joystick devices get the rest.
+    return INPUT_DEVICE_CLASS_JOYSTICK;
+}
+
+static bool getBooleanProperty(const InputProperty& prop) {
+    const char* propValue = prop.getValue();
+    if (propValue == nullptr) return false;
+
+    char* end;
+    int value = std::strtol(propValue, &end, 10);
+    if (*end != '\0') {
+        ALOGW("Expected boolean for property %s; value=%s", prop.getKey(), propValue);
+        return false;
+    }
+    return value;
+}
+
+static void setDeviceClasses(const InputDeviceNode* node, uint32_t* classes) {
+    // See if this is a keyboard. Ignore everything in the button range except
+    // for joystick and gamepad buttons which are handled like keyboards for the
+    // most part.
+    bool haveKeyboardKeys = node->hasKeyInRange(0, BTN_MISC) ||
+        node->hasKeyInRange(KEY_OK, KEY_CNT);
+    bool haveGamepadButtons = node->hasKeyInRange(BTN_MISC, BTN_MOUSE) ||
+        node->hasKeyInRange(BTN_JOYSTICK, BTN_DIGI);
+    if (haveKeyboardKeys || haveGamepadButtons) {
+        *classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+    }
+
+    // See if this is a cursor device such as a trackball or mouse.
+    if (node->hasKey(BTN_MOUSE)
+            && node->hasRelativeAxis(REL_X)
+            && node->hasRelativeAxis(REL_Y)) {
+        *classes |= INPUT_DEVICE_CLASS_CURSOR;
+    }
+
+    // See if this is a touch pad.
+    // Is this a new modern multi-touch driver?
+    if (node->hasAbsoluteAxis(ABS_MT_POSITION_X)
+            && node->hasAbsoluteAxis(ABS_MT_POSITION_Y)) {
+        // Some joysticks such as the PS3 controller report axes that conflict
+        // with the ABS_MT range. Try to confirm that the device really is a
+        // touch screen.
+        if (node->hasKey(BTN_TOUCH) || !haveGamepadButtons) {
+            *classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
+        }
+    // Is this an old style single-touch driver?
+    } else if (node->hasKey(BTN_TOUCH)
+            && node->hasAbsoluteAxis(ABS_X)
+            && node->hasAbsoluteAxis(ABS_Y)) {
+        *classes != INPUT_DEVICE_CLASS_TOUCH;
+    // Is this a BT stylus?
+    } else if ((node->hasAbsoluteAxis(ABS_PRESSURE) || node->hasKey(BTN_TOUCH))
+            && !node->hasAbsoluteAxis(ABS_X) && !node->hasAbsoluteAxis(ABS_Y)) {
+        *classes |= INPUT_DEVICE_CLASS_EXTERNAL_STYLUS;
+        // Keyboard will try to claim some of the buttons but we really want to
+        // reserve those so we can fuse it with the touch screen data, so just
+        // take them back. Note this means an external stylus cannot also be a
+        // keyboard device.
+        *classes &= ~INPUT_DEVICE_CLASS_KEYBOARD;
+    }
+
+    // See if this device is a joystick.
+    // Assumes that joysticks always have gamepad buttons in order to
+    // distinguish them from other devices such as accelerometers that also have
+    // absolute axes.
+    if (haveGamepadButtons) {
+        uint32_t assumedClasses = *classes | INPUT_DEVICE_CLASS_JOYSTICK;
+        for (int i = 0; i < ABS_CNT; ++i) {
+            if (node->hasAbsoluteAxis(i)
+                    && getAbsAxisUsage(i, assumedClasses) == INPUT_DEVICE_CLASS_JOYSTICK) {
+                *classes = assumedClasses;
+                break;
+            }
+        }
+    }
+
+    // Check whether this device has switches.
+    for (int i = 0; i < SW_CNT; ++i) {
+        if (node->hasSwitch(i)) {
+            *classes |= INPUT_DEVICE_CLASS_SWITCH;
+            break;
+        }
+    }
+
+    // Check whether this device supports the vibrator.
+    if (node->hasForceFeedback(FF_RUMBLE)) {
+        *classes |= INPUT_DEVICE_CLASS_VIBRATOR;
+    }
+
+    // If the device isn't recognized as something we handle, don't monitor it.
+    // TODO
+
+    ALOGD("device %s classes=0x%x", node->getPath().c_str(), *classes);
+}
+
+EvdevDevice::EvdevDevice(InputHost host, const std::shared_ptr<InputDeviceNode>& node) :
+    mHost(host), mDeviceNode(node) {
+
+    InputBus bus = getInputBus(node);
+    mInputId = mHost.createDeviceIdentifier(
+            node->getName().c_str(),
+            node->getProductId(),
+            node->getVendorId(),
+            bus,
+            node->getUniqueId().c_str());
+
+    InputPropertyMap propMap = mHost.getDevicePropertyMap(mInputId);
+    setDeviceClasses(mDeviceNode.get(), &mClasses);
+}
 
 void EvdevDevice::processInput(InputEvent& event, nsecs_t currentTime) {
+#if DEBUG_INPUT_EVENTS
     std::string log;
     log.append("---InputEvent for device %s---\n");
     log.append("   when:  %" PRId64 "\n");
     log.append("   type:  %d\n");
     log.append("   code:  %d\n");
     log.append("   value: %d\n");
-    ALOGV(log.c_str(), mDeviceNode->getPath().c_str(), event.when, event.type, event.code,
+    ALOGD(log.c_str(), mDeviceNode->getPath().c_str(), event.when, event.type, event.code,
             event.value);
+#endif
 
     if (event.type == EV_MSC) {
         if (event.code == MSC_ANDROID_TIME_SEC) {