Add keycodes and meta-key modifiers to support external keyboards.

Added new key maps for external keyboards.  These maps are intended to
be shared across devices by inheriting the "keyboards.mk" product
makefile as part of the device's product definition.

One of the trickier changes here was to unwind some code in
MetaKeyKeyListener that assumed that only the low 8 bits of the meta key
state were actually used.  The new code abandons bitshifts in favor
of simple conditionals that are probably easier to read anyways.
The special meta key state constants used by MetaKeyKeyListener
are now (@hide) defined in KeyEvent now so as to make it clearer that they
share the same code space even if those codes are not valid for KeyEvents.

The EventHub now takes care of detecting the appropriate key layout
map and key character map when the device is added and sets system
properties accordingly.  This avoids having duplicate code in
KeyCharacterMap to probe for the appropriate key character map
although the current probing mechanism has been preserved for legacy
reasons just in case.

Added support for tracking caps lock, num lock and scroll lock and
turning their corresponding LEDs on and off as needed.

The key character map format will need to be updated to correctly support
PC style external keyboard semantics related to modifier keys.
That will come in a later change so caps lock doesn't actually do
anything right now except turn the shiny LEDs on and off...

Added a list of symbolic key names to KeyEvent and improved the toString()
output for debug diagnosis.  Having this list in a central place in the
framework also allows us to remove it from Monkey so there is one less
thing to maintain when we add new keycodes.

Bug: 2912307
Change-Id: If8c25e8d50a7c29bbf5d663c94284f5f86de5da4
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 944731d..97a7528 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -94,7 +94,7 @@
 
 EventHub::device_t::device_t(int32_t _id, const char* _path, const char* name)
     : id(_id), path(_path), name(name), classes(0)
-    , keyBitmask(NULL), layoutMap(new KeyLayoutMap()), fd(-1), next(NULL) {
+    , keyBitmask(NULL), layoutMap(new KeyLayoutMap()), defaultKeyMap(false), fd(-1), next(NULL) {
 }
 
 EventHub::device_t::~device_t() {
@@ -103,7 +103,7 @@
 }
 
 EventHub::EventHub(void)
-    : mError(NO_INIT), mHaveFirstKeyboard(false), mFirstKeyboardId(0)
+    : mError(NO_INIT), mHaveFirstKeyboard(false), mFirstKeyboardId(-1)
     , mDevicesById(0), mNumDevicesById(0)
     , mOpeningDevices(0), mClosingDevices(0)
     , mDevices(0), mFDs(0), mFDCount(0), mOpened(false), mNeedToSendFinishedDeviceScan(false)
@@ -325,6 +325,39 @@
     mExcludedDevices.push_back(name);
 }
 
+bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
+    AutoMutex _l(mLock);
+    device_t* device = getDeviceLocked(deviceId);
+    if (device) {
+        uint8_t bitmask[sizeof_bit_array(LED_MAX + 1)];
+        memset(bitmask, 0, sizeof(bitmask));
+        if (ioctl(device->fd, EVIOCGBIT(EV_LED, sizeof(bitmask)), bitmask) >= 0) {
+            if (test_bit(led, bitmask)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+    AutoMutex _l(mLock);
+    device_t* device = getDeviceLocked(deviceId);
+    if (device) {
+        struct input_event ev;
+        ev.time.tv_sec = 0;
+        ev.time.tv_usec = 0;
+        ev.type = EV_LED;
+        ev.code = led;
+        ev.value = on ? 1 : 0;
+
+        ssize_t nWrite;
+        do {
+            nWrite = write(device->fd, &ev, sizeof(struct input_event));
+        } while (nWrite == -1 && errno == EINTR);
+    }
+}
+
 EventHub::device_t* EventHub::getDeviceLocked(int32_t deviceId) const
 {
     if (deviceId == 0) deviceId = mFirstKeyboardId;
@@ -760,54 +793,42 @@
 #endif
 
     if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
-        char tmpfn[sizeof(name)];
-        char keylayoutFilename[300];
-
         // a more descriptive name
         device->name = name;
 
-        // replace all the spaces with underscores
-        strcpy(tmpfn, name);
-        for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' '))
-            *p = '_';
+        // Configure the keymap for the device.
+        configureKeyMap(device);
 
-        // find the .kl file we need for this device
-        const char* root = getenv("ANDROID_ROOT");
-        snprintf(keylayoutFilename, sizeof(keylayoutFilename),
-                 "%s/usr/keylayout/%s.kl", root, tmpfn);
-        bool defaultKeymap = false;
-        if (access(keylayoutFilename, R_OK)) {
-            snprintf(keylayoutFilename, sizeof(keylayoutFilename),
-                     "%s/usr/keylayout/%s", root, "qwerty.kl");
-            defaultKeymap = true;
-        }
-        status_t status = device->layoutMap->load(keylayoutFilename);
-        if (status) {
-            LOGE("Error %d loading key layout.", status);
-        }
-
-        // tell the world about the devname (the descriptive name)
-        if (!mHaveFirstKeyboard && !defaultKeymap && strstr(name, "-keypad")) {
+        // Tell the world about the devname (the descriptive name)
+        if (!mHaveFirstKeyboard && !device->defaultKeyMap && strstr(name, "-keypad")) {
             // the built-in keyboard has a well-known device ID of 0,
             // this device better not go away.
             mHaveFirstKeyboard = true;
             mFirstKeyboardId = device->id;
-            property_set("hw.keyboards.0.devname", name);
+            setKeyboardProperties(device, true);
         } else {
             // ensure mFirstKeyboardId is set to -something-.
-            if (mFirstKeyboardId == 0) {
+            if (mFirstKeyboardId == -1) {
                 mFirstKeyboardId = device->id;
+                setKeyboardProperties(device, true);
             }
         }
-        char propName[100];
-        sprintf(propName, "hw.keyboards.%u.devname", device->id);
-        property_set(propName, name);
+        setKeyboardProperties(device, false);
+
+        // Load the keylayout.
+        if (!device->keyLayoutFilename.isEmpty()) {
+            status_t status = device->layoutMap->load(device->keyLayoutFilename);
+            if (status) {
+                LOGE("Error %d loading key layout file '%s'.", status,
+                        device->keyLayoutFilename.string());
+            }
+        }
 
         // 'Q' key support = cheap test of whether this is an alpha-capable kbd
         if (hasKeycodeLocked(device, AKEYCODE_Q)) {
             device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
         }
-        
+
         // See if this device has a DPAD.
         if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
                 hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
@@ -816,7 +837,7 @@
                 hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
             device->classes |= INPUT_DEVICE_CLASS_DPAD;
         }
-        
+
         // See if this device has a gamepad.
         for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES); i++) {
             if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
@@ -825,8 +846,9 @@
             }
         }
 
-        LOGI("New keyboard: device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n",
-                device->id, name, propName, keylayoutFilename);
+        LOGI("New keyboard: device->id=0x%x devname='%s' keylayout='%s' keycharactermap='%s'\n",
+                device->id, name,
+                device->keyLayoutFilename.string(), device->keyCharacterMapFilename.string());
     }
 
     // If the device isn't recognized as something we handle, don't monitor it.
@@ -852,6 +874,109 @@
     return 0;
 }
 
+void EventHub::configureKeyMap(device_t* device) {
+    // As an initial key map name, try using the device name.
+    String8 keyMapName(device->name);
+    char* p = keyMapName.lockBuffer(keyMapName.size());
+    while (*p) {
+        if (*p == ' ') *p = '_';
+        p++;
+    }
+    keyMapName.unlockBuffer();
+
+    if (probeKeyMap(device, keyMapName, false)) return;
+
+    // TODO Consider allowing the user to configure a specific key map somehow.
+
+    // Try the Generic key map.
+    // TODO Apply some additional heuristics here to figure out what kind of
+    //      generic key map to use (US English, etc.).
+    keyMapName.setTo("Generic");
+    if (probeKeyMap(device, keyMapName, true)) return;
+
+    // Fall back on the old style catchall qwerty key map.
+    keyMapName.setTo("qwerty");
+    if (probeKeyMap(device, keyMapName, true)) return;
+
+    // Give up!
+    keyMapName.setTo("unknown");
+    selectKeyMap(device, keyMapName, true);
+    LOGE("Could not determine key map for device '%s'.", device->name.string());
+}
+
+bool EventHub::probeKeyMap(device_t* device, const String8& keyMapName, bool defaultKeyMap) {
+    const char* root = getenv("ANDROID_ROOT");
+
+    // TODO Consider also looking somewhere in a writeable partition like /data for a
+    //      custom keymap supplied by the user for this device.
+    bool haveKeyLayout = !device->keyLayoutFilename.isEmpty();
+    if (!haveKeyLayout) {
+        device->keyLayoutFilename.setTo(root);
+        device->keyLayoutFilename.append("/usr/keylayout/");
+        device->keyLayoutFilename.append(keyMapName);
+        device->keyLayoutFilename.append(".kl");
+        if (access(device->keyLayoutFilename.string(), R_OK)) {
+            device->keyLayoutFilename.clear();
+        } else {
+            haveKeyLayout = true;
+        }
+    }
+
+    bool haveKeyCharacterMap = !device->keyCharacterMapFilename.isEmpty();
+    if (!haveKeyCharacterMap) {
+        device->keyCharacterMapFilename.setTo(root);
+        device->keyCharacterMapFilename.append("/usr/keychars/");
+        device->keyCharacterMapFilename.append(keyMapName);
+        device->keyCharacterMapFilename.append(".kcm.bin");
+        if (access(device->keyCharacterMapFilename.string(), R_OK)) {
+            device->keyCharacterMapFilename.clear();
+        } else {
+            haveKeyCharacterMap = true;
+        }
+    }
+
+    if (haveKeyLayout || haveKeyCharacterMap) {
+        selectKeyMap(device, keyMapName, defaultKeyMap);
+    }
+    return haveKeyLayout && haveKeyCharacterMap;
+}
+
+void EventHub::selectKeyMap(device_t* device,
+        const String8& keyMapName, bool defaultKeyMap) {
+    if (device->keyMapName.isEmpty()) {
+        device->keyMapName.setTo(keyMapName);
+        device->defaultKeyMap = defaultKeyMap;
+    }
+}
+
+void EventHub::setKeyboardProperties(device_t* device, bool firstKeyboard) {
+    int32_t id = firstKeyboard ? 0 : device->id;
+
+    char propName[100];
+    sprintf(propName, "hw.keyboards.%u.devname", id);
+    property_set(propName, device->name.string());
+    sprintf(propName, "hw.keyboards.%u.keymap", id);
+    property_set(propName, device->keyMapName.string());
+    sprintf(propName, "hw.keyboards.%u.klfile", id);
+    property_set(propName, device->keyLayoutFilename.string());
+    sprintf(propName, "hw.keyboards.%u.kcmfile", id);
+    property_set(propName, device->keyCharacterMapFilename.string());
+}
+
+void EventHub::clearKeyboardProperties(device_t* device, bool firstKeyboard) {
+    int32_t id = firstKeyboard ? 0 : device->id;
+
+    char propName[100];
+    sprintf(propName, "hw.keyboards.%u.devname", id);
+    property_set(propName, "");
+    sprintf(propName, "hw.keyboards.%u.keymap", id);
+    property_set(propName, "");
+    sprintf(propName, "hw.keyboards.%u.klfile", id);
+    property_set(propName, "");
+    sprintf(propName, "hw.keyboards.%u.kcmfile", id);
+    property_set(propName, "");
+}
+
 bool EventHub::hasKeycodeLocked(device_t* device, int keycode) const
 {
     if (device->keyBitmask == NULL || device->layoutMap == NULL) {
@@ -909,13 +1034,10 @@
             if (device->id == mFirstKeyboardId) {
                 LOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
                         device->path.string(), mFirstKeyboardId);
-                mFirstKeyboardId = 0;
-                property_set("hw.keyboards.0.devname", NULL);
+                mFirstKeyboardId = -1;
+                clearKeyboardProperties(device, true);
             }
-            // clear the property
-            char propName[100];
-            sprintf(propName, "hw.keyboards.%u.devname", device->id);
-            property_set(propName, NULL);
+            clearKeyboardProperties(device, false);
             return 0;
         }
     }
@@ -1014,7 +1136,11 @@
                 }
                 dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
                 dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
-                dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n", device->keylayoutFilename.string());
+                dump.appendFormat(INDENT3 "KeyMapName: %s\n", device->keyMapName.string());
+                dump.appendFormat(INDENT3 "KeyLayoutFilename: %s\n",
+                        device->keyLayoutFilename.string());
+                dump.appendFormat(INDENT3 "KeyCharacterMapFilename: %s\n",
+                        device->keyCharacterMapFilename.string());
             }
         }
     } // release lock