diff options
Diffstat (limited to 'libs/ui')
-rw-r--r-- | libs/ui/Android.mk | 1 | ||||
-rw-r--r-- | libs/ui/EventHub.cpp | 154 | ||||
-rw-r--r-- | libs/ui/GraphicBuffer.cpp | 20 | ||||
-rw-r--r-- | libs/ui/Input.cpp | 8 | ||||
-rw-r--r-- | libs/ui/InputDispatcher.cpp | 197 | ||||
-rw-r--r-- | libs/ui/InputReader.cpp | 117 | ||||
-rw-r--r-- | libs/ui/InputTransport.cpp | 27 | ||||
-rw-r--r-- | libs/ui/KeyCharacterMap.cpp | 938 | ||||
-rw-r--r-- | libs/ui/KeyLayoutMap.cpp | 373 | ||||
-rw-r--r-- | libs/ui/KeyLayoutMap.h | 31 | ||||
-rw-r--r-- | libs/ui/Keyboard.cpp | 244 | ||||
-rw-r--r-- | libs/ui/tests/InputDispatcher_test.cpp | 5 | ||||
-rw-r--r-- | libs/ui/tests/InputPublisherAndConsumer_test.cpp | 14 | ||||
-rw-r--r-- | libs/ui/tests/InputReader_test.cpp | 111 |
14 files changed, 1668 insertions, 572 deletions
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk index c4a09d6..61d8abd 100644 --- a/libs/ui/Android.mk +++ b/libs/ui/Android.mk @@ -10,6 +10,7 @@ LOCAL_SRC_FILES:= \ GraphicBufferAllocator.cpp \ GraphicBufferMapper.cpp \ GraphicLog.cpp \ + Keyboard.cpp \ KeyLayoutMap.cpp \ KeyCharacterMap.cpp \ Input.cpp \ diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp index 41daa9c..f468217 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -16,7 +16,6 @@ //#define LOG_NDEBUG 0 #include <ui/EventHub.h> -#include <ui/KeycodeLabels.h> #include <hardware_legacy/power.h> #include <cutils/properties.h> @@ -33,7 +32,7 @@ #include <errno.h> #include <assert.h> -#include "KeyLayoutMap.h" +#include <ui/KeyLayoutMap.h> #include <string.h> #include <stdint.h> @@ -94,7 +93,7 @@ static inline const char* toString(bool value) { 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(NULL), fd(-1), next(NULL) { } EventHub::device_t::~device_t() { @@ -103,7 +102,7 @@ EventHub::device_t::~device_t() { } 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) @@ -204,8 +203,12 @@ int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const { } int32_t EventHub::getKeyCodeStateLocked(device_t* device, int32_t keyCode) const { + if (!device->layoutMap) { + return AKEY_STATE_UNKNOWN; + } + Vector<int32_t> scanCodes; - device->layoutMap->findScancodes(keyCode, &scanCodes); + device->layoutMap->findScanCodes(keyCode, &scanCodes); uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; memset(key_bitmask, 0, sizeof(key_bitmask)); @@ -273,7 +276,7 @@ bool EventHub::markSupportedKeyCodesLocked(device_t* device, size_t numCodes, for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { scanCodes.clear(); - status_t err = device->layoutMap->findScancodes(keyCodes[codeIndex], &scanCodes); + status_t err = device->layoutMap->findScanCodes(keyCodes[codeIndex], &scanCodes); if (! err) { // check the possible scan codes identified by the layout map against the // map of codes actually emitted by the driver @@ -325,6 +328,39 @@ void EventHub::addExcludedDevice(const char* deviceName) 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; @@ -415,14 +451,14 @@ bool EventHub::getEvent(RawEvent* outEvent) } outEvent->type = iev.type; outEvent->scanCode = iev.code; + outEvent->flags = 0; if (iev.type == EV_KEY) { - status_t err = device->layoutMap->map(iev.code, - & outEvent->keyCode, & outEvent->flags); - LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", - iev.code, outEvent->keyCode, outEvent->flags, err); - if (err != 0) { - outEvent->keyCode = AKEYCODE_UNKNOWN; - outEvent->flags = 0; + outEvent->keyCode = AKEYCODE_UNKNOWN; + if (device->layoutMap) { + status_t err = device->layoutMap->map(iev.code, + &outEvent->keyCode, &outEvent->flags); + LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", + iev.code, outEvent->keyCode, outEvent->flags, err); } } else { outEvent->keyCode = iev.code; @@ -763,54 +799,44 @@ int EventHub::openDevice(const char *deviceName) { #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 = '_'; - - // 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); - } + // Configure the keymap for the device. + + configureKeyMap(device); - // 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->keyMapInfo.isDefaultKeyMap && 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); + } + } + setKeyboardProperties(device, false); + + // Load the keylayout. + if (!device->keyMapInfo.keyLayoutFile.isEmpty()) { + status_t status = KeyLayoutMap::load(device->keyMapInfo.keyLayoutFile, + &device->layoutMap); + if (status) { + LOGE("Error %d loading key layout file '%s'.", status, + device->keyMapInfo.keyLayoutFile.string()); } } - char propName[100]; - sprintf(propName, "hw.keyboards.%u.devname", device->id); - property_set(propName, name); // '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) && @@ -819,7 +845,7 @@ int EventHub::openDevice(const char *deviceName) { 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)/sizeof(GAMEPAD_KEYCODES[0]); i++) { if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) { @@ -828,8 +854,10 @@ int EventHub::openDevice(const char *deviceName) { } } - 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->keyMapInfo.keyLayoutFile.string(), + device->keyMapInfo.keyCharacterMapFile.string()); } // If the device isn't recognized as something we handle, don't monitor it. @@ -855,6 +883,20 @@ int EventHub::openDevice(const char *deviceName) { return 0; } +void EventHub::configureKeyMap(device_t* device) { + android::resolveKeyMap(device->name, device->keyMapInfo); +} + +void EventHub::setKeyboardProperties(device_t* device, bool firstKeyboard) { + int32_t id = firstKeyboard ? 0 : device->id; + android::setKeyboardProperties(id, device->name, device->keyMapInfo); +} + +void EventHub::clearKeyboardProperties(device_t* device, bool firstKeyboard) { + int32_t id = firstKeyboard ? 0 : device->id; + android::clearKeyboardProperties(id); +} + bool EventHub::hasKeycodeLocked(device_t* device, int keycode) const { if (device->keyBitmask == NULL || device->layoutMap == NULL) { @@ -862,7 +904,7 @@ bool EventHub::hasKeycodeLocked(device_t* device, int keycode) const } Vector<int32_t> scanCodes; - device->layoutMap->findScancodes(keycode, &scanCodes); + device->layoutMap->findScanCodes(keycode, &scanCodes); const size_t N = scanCodes.size(); for (size_t i=0; i<N && i<=KEY_MAX; i++) { int32_t sc = scanCodes.itemAt(i); @@ -912,13 +954,10 @@ int EventHub::closeDevice(const char *deviceName) { 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; } } @@ -1017,7 +1056,14 @@ void EventHub::dump(String8& dump) { } 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 "IsDefaultKeyMap: %s\n", + toString(device->keyMapInfo.isDefaultKeyMap)); + dump.appendFormat(INDENT3 "KeyMapName: %s\n", + device->keyMapInfo.keyMapName.string()); + dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n", + device->keyMapInfo.keyLayoutFile.string()); + dump.appendFormat(INDENT3 "KeyCharacterMapFile: %s\n", + device->keyMapInfo.keyCharacterMapFile.string()); } } } // release lock diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp index 519c277..436e064 100644 --- a/libs/ui/GraphicBuffer.cpp +++ b/libs/ui/GraphicBuffer.cpp @@ -77,6 +77,19 @@ GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h, handle = inHandle; } +GraphicBuffer::GraphicBuffer(android_native_buffer_t* buffer, bool keepOwnership) + : BASE(), mOwner(keepOwnership ? ownHandle : ownNone), + mBufferMapper(GraphicBufferMapper::get()), + mInitCheck(NO_ERROR), mIndex(-1), mWrappedBuffer(buffer) +{ + width = buffer->width; + height = buffer->height; + stride = buffer->stride; + format = buffer->format; + usage = buffer->usage; + handle = buffer->handle; +} + GraphicBuffer::~GraphicBuffer() { if (handle) { @@ -87,12 +100,14 @@ GraphicBuffer::~GraphicBuffer() void GraphicBuffer::free_handle() { if (mOwner == ownHandle) { + mBufferMapper.unregisterBuffer(handle); native_handle_close(handle); native_handle_delete(const_cast<native_handle*>(handle)); } else if (mOwner == ownData) { GraphicBufferAllocator& allocator(GraphicBufferAllocator::get()); allocator.free(handle); } + mWrappedBuffer = 0; } status_t GraphicBuffer::initCheck() const { @@ -253,6 +268,11 @@ status_t GraphicBuffer::unflatten(void const* buffer, size_t size, } mOwner = ownHandle; + + if (handle != 0) { + mBufferMapper.registerBuffer(handle); + } + return NO_ERROR; } diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp index 811edaf..944a79b 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -33,6 +33,7 @@ bool KeyEvent::hasDefaultAction(int32_t keyCode) { case AKEYCODE_ENDCALL: case AKEYCODE_VOLUME_UP: case AKEYCODE_VOLUME_DOWN: + case AKEYCODE_VOLUME_MUTE: case AKEYCODE_POWER: case AKEYCODE_CAMERA: case AKEYCODE_HEADSETHOOK: @@ -40,11 +41,14 @@ bool KeyEvent::hasDefaultAction(int32_t keyCode) { case AKEYCODE_NOTIFICATION: case AKEYCODE_FOCUS: case AKEYCODE_SEARCH: + case AKEYCODE_MEDIA_PLAY: + case AKEYCODE_MEDIA_PAUSE: case AKEYCODE_MEDIA_PLAY_PAUSE: case AKEYCODE_MEDIA_STOP: case AKEYCODE_MEDIA_NEXT: case AKEYCODE_MEDIA_PREVIOUS: case AKEYCODE_MEDIA_REWIND: + case AKEYCODE_MEDIA_RECORD: case AKEYCODE_MEDIA_FAST_FORWARD: case AKEYCODE_MUTE: return true; @@ -67,14 +71,18 @@ bool KeyEvent::isSystemKey(int32_t keyCode) { case AKEYCODE_ENDCALL: case AKEYCODE_VOLUME_UP: case AKEYCODE_VOLUME_DOWN: + case AKEYCODE_VOLUME_MUTE: case AKEYCODE_MUTE: case AKEYCODE_POWER: case AKEYCODE_HEADSETHOOK: + case AKEYCODE_MEDIA_PLAY: + case AKEYCODE_MEDIA_PAUSE: case AKEYCODE_MEDIA_PLAY_PAUSE: case AKEYCODE_MEDIA_STOP: case AKEYCODE_MEDIA_NEXT: case AKEYCODE_MEDIA_PREVIOUS: case AKEYCODE_MEDIA_REWIND: + case AKEYCODE_MEDIA_RECORD: case AKEYCODE_MEDIA_FAST_FORWARD: case AKEYCODE_CAMERA: case AKEYCODE_FOCUS: diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp index 28ccc43..db7d448 100644 --- a/libs/ui/InputDispatcher.cpp +++ b/libs/ui/InputDispatcher.cpp @@ -51,9 +51,6 @@ namespace android { -// Delay before reporting long touch events to the power manager. -const nsecs_t LONG_TOUCH_DELAY = 300 * 1000000LL; // 300 ms - // Default input dispatching timeout if there is no focused application or paused window // from which to determine an appropriate dispatching timeout. const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec @@ -160,6 +157,10 @@ bool InputWindow::isTrustedOverlay() const { || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY; } +bool InputWindow::supportsSplitTouch() const { + return layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH; +} + // --- InputDispatcher --- @@ -1113,8 +1114,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, } // Figure out whether splitting will be allowed for this window. - if (newTouchedWindow - && (newTouchedWindow->layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH)) { + if (newTouchedWindow && newTouchedWindow->supportsSplitTouch()) { // New window supports splitting. isSplit = true; } else if (isSplit) { @@ -1163,7 +1163,10 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, // If the pointer is not currently down, then ignore the event. if (! mTempTouchState.down) { - LOGI("Dropping event because the pointer is not down."); +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Dropping event because the pointer is not down or we previously " + "dropped the pointer down event."); +#endif injectionResult = INPUT_EVENT_INJECTION_FAILED; goto Failed; } @@ -1405,25 +1408,25 @@ String8 InputDispatcher::getApplicationWindowLabelLocked(const InputApplication* void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) { int32_t eventType = POWER_MANAGER_BUTTON_EVENT; - if (eventEntry->type == EventEntry::TYPE_MOTION) { + switch (eventEntry->type) { + case EventEntry::TYPE_MOTION: { const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry); + if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) { + return; + } + if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) { - switch (motionEntry->action) { - case AMOTION_EVENT_ACTION_DOWN: - eventType = POWER_MANAGER_TOUCH_EVENT; - break; - case AMOTION_EVENT_ACTION_UP: - eventType = POWER_MANAGER_TOUCH_UP_EVENT; - break; - default: - if (motionEntry->eventTime - motionEntry->downTime < LONG_TOUCH_DELAY) { - eventType = POWER_MANAGER_TOUCH_EVENT; - } else { - eventType = POWER_MANAGER_LONG_TOUCH_EVENT; - } - break; - } + eventType = POWER_MANAGER_TOUCH_EVENT; } + break; + } + case EventEntry::TYPE_KEY: { + const KeyEntry* keyEntry = static_cast<const KeyEntry*>(eventEntry); + if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) { + return; + } + break; + } } CommandEntry* commandEntry = postCommandLocked( @@ -1753,13 +1756,14 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, } void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, - const sp<Connection>& connection) { + const sp<Connection>& connection, bool handled) { #if DEBUG_DISPATCH_CYCLE LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, " - "%01.1fms since dispatch", + "%01.1fms since dispatch, handled=%s", connection->getInputChannelName(), connection->getEventLatencyMillis(currentTime), - connection->getDispatchLatencyMillis(currentTime)); + connection->getDispatchLatencyMillis(currentTime), + toString(handled)); #endif if (connection->status == Connection::STATUS_BROKEN @@ -1767,9 +1771,6 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, return; } - // Notify other system components. - onDispatchCycleFinishedLocked(currentTime, connection); - // Reset the publisher since the event has been consumed. // We do this now so that the publisher can release some of its internal resources // while waiting for the next dispatch cycle to begin. @@ -1781,7 +1782,8 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, return; } - startNextDispatchCycleLocked(currentTime, connection); + // Notify other system components and prepare to start the next dispatch cycle. + onDispatchCycleFinishedLocked(currentTime, connection, handled); } void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime, @@ -1881,7 +1883,8 @@ int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data return 1; } - status_t status = connection->inputPublisher.receiveFinishedSignal(); + bool handled = false; + status_t status = connection->inputPublisher.receiveFinishedSignal(handled); if (status) { LOGE("channel '%s' ~ Failed to receive finished signal. status=%d", connection->getInputChannelName(), status); @@ -1890,7 +1893,7 @@ int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data return 0; // remove the callback } - d->finishDispatchCycleLocked(currentTime, connection); + d->finishDispatchCycleLocked(currentTime, connection, handled); d->runCommandsLockedInterruptible(); return 1; } // release lock @@ -2615,6 +2618,77 @@ void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { } } +bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) { +#if DEBUG_FOCUS + LOGD("transferTouchFocus: fromChannel=%s, toChannel=%s", + fromChannel->getName().string(), toChannel->getName().string()); +#endif + { // acquire lock + AutoMutex _l(mLock); + + const InputWindow* fromWindow = getWindowLocked(fromChannel); + const InputWindow* toWindow = getWindowLocked(toChannel); + if (! fromWindow || ! toWindow) { +#if DEBUG_FOCUS + LOGD("Cannot transfer focus because from or to window not found."); +#endif + return false; + } + if (fromWindow == toWindow) { +#if DEBUG_FOCUS + LOGD("Trivial transfer to same window."); +#endif + return true; + } + + bool found = false; + for (size_t i = 0; i < mTouchState.windows.size(); i++) { + const TouchedWindow& touchedWindow = mTouchState.windows[i]; + if (touchedWindow.window == fromWindow) { + int32_t oldTargetFlags = touchedWindow.targetFlags; + BitSet32 pointerIds = touchedWindow.pointerIds; + + mTouchState.windows.removeAt(i); + + int32_t newTargetFlags = oldTargetFlags + & (InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_SPLIT); + mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds); + + found = true; + break; + } + } + + if (! found) { +#if DEBUG_FOCUS + LOGD("Focus transfer failed because from window did not have focus."); +#endif + return false; + } + + ssize_t fromConnectionIndex = getConnectionIndexLocked(fromChannel); + ssize_t toConnectionIndex = getConnectionIndexLocked(toChannel); + if (fromConnectionIndex >= 0 && toConnectionIndex >= 0) { + sp<Connection> fromConnection = mConnectionsByReceiveFd.valueAt(fromConnectionIndex); + sp<Connection> toConnection = mConnectionsByReceiveFd.valueAt(toConnectionIndex); + + fromConnection->inputState.copyPointerStateTo(toConnection->inputState); + synthesizeCancelationEventsForConnectionLocked(fromConnection, + InputState::CANCEL_POINTER_EVENTS, + "transferring touch focus from this window to another window"); + } + +#if DEBUG_FOCUS + logDispatchStateLocked(); +#endif + } // release lock + + // Wake up poll loop since it may need to make new input dispatching choices. + mLooper->wake(); + return true; +} + void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { #if DEBUG_FOCUS LOGD("Resetting and dropping all events (%s).", reason); @@ -2721,7 +2795,7 @@ void InputDispatcher::dumpDispatchStateLocked(String8& dump) { dump.append(INDENT "ActiveConnections:\n"); for (size_t i = 0; i < mActiveConnections.size(); i++) { const Connection* connection = mActiveConnections[i]; - dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u" + dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u, " "inputState.isNeutral=%s\n", i, connection->getInputChannelName(), connection->getStatusLabel(), connection->outboundQueue.count(), @@ -2852,7 +2926,11 @@ void InputDispatcher::onDispatchCycleStartedLocked( } void InputDispatcher::onDispatchCycleFinishedLocked( - nsecs_t currentTime, const sp<Connection>& connection) { + nsecs_t currentTime, const sp<Connection>& connection, bool handled) { + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doDispatchCycleFinishedLockedInterruptible); + commandEntry->connection = connection; + commandEntry->handled = handled; } void InputDispatcher::onDispatchCycleBrokenLocked( @@ -2921,9 +2999,7 @@ void InputDispatcher::doNotifyANRLockedInterruptible( void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( CommandEntry* commandEntry) { KeyEntry* entry = commandEntry->keyEntry; - mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags, - entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, - entry->downTime, entry->eventTime); + initializeKeyEvent(&mReusableKeyEvent, entry); mLock.unlock(); @@ -2938,6 +3014,31 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( mAllocator.releaseKeyEntry(entry); } +void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( + CommandEntry* commandEntry) { + sp<Connection> connection = commandEntry->connection; + bool handled = commandEntry->handled; + + if (!handled && !connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; + if (dispatchEntry->inProgress + && dispatchEntry->hasForegroundTarget() + && dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) { + KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); + initializeKeyEvent(&mReusableKeyEvent, keyEntry); + + mLock.unlock(); + + mPolicy->dispatchUnhandledKey(connection->inputChannel, + & mReusableKeyEvent, keyEntry->policyFlags); + + mLock.lock(); + } + } + + startNextDispatchCycleLocked(now(), connection); +} + void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) { mLock.unlock(); @@ -2946,6 +3047,12 @@ void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* comman mLock.lock(); } +void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) { + event->initialize(entry->deviceId, entry->source, entry->action, entry->flags, + entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, + entry->downTime, entry->eventTime); +} + void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry, int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) { // TODO Write some statistics about how long we spend waiting. @@ -3356,6 +3463,24 @@ void InputDispatcher::InputState::clear() { mMotionMementos.clear(); } +void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const { + for (size_t i = 0; i < mMotionMementos.size(); i++) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + if (memento.source & AINPUT_SOURCE_CLASS_POINTER) { + for (size_t j = 0; j < other.mMotionMementos.size(); ) { + const MotionMemento& otherMemento = other.mMotionMementos.itemAt(j); + if (memento.deviceId == otherMemento.deviceId + && memento.source == otherMemento.source) { + other.mMotionMementos.removeAt(j); + } else { + j += 1; + } + } + other.mMotionMementos.push(memento); + } + } +} + bool InputDispatcher::InputState::shouldCancelEvent(int32_t eventSource, CancelationOptions options) { switch (options) { diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp index 3197ab2..daff2d0 100644 --- a/libs/ui/InputReader.cpp +++ b/libs/ui/InputReader.cpp @@ -24,6 +24,7 @@ #include <cutils/log.h> #include <ui/InputReader.h> +#include <ui/Keyboard.h> #include <stddef.h> #include <stdlib.h> @@ -70,43 +71,6 @@ static inline const char* toString(bool value) { return value ? "true" : "false"; } - -int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) { - int32_t mask; - switch (keyCode) { - case AKEYCODE_ALT_LEFT: - mask = AMETA_ALT_LEFT_ON; - break; - case AKEYCODE_ALT_RIGHT: - mask = AMETA_ALT_RIGHT_ON; - break; - case AKEYCODE_SHIFT_LEFT: - mask = AMETA_SHIFT_LEFT_ON; - break; - case AKEYCODE_SHIFT_RIGHT: - mask = AMETA_SHIFT_RIGHT_ON; - break; - case AKEYCODE_SYM: - mask = AMETA_SYM_ON; - break; - default: - return oldMetaState; - } - - int32_t newMetaState = down ? oldMetaState | mask : oldMetaState & ~ mask - & ~ (AMETA_ALT_ON | AMETA_SHIFT_ON); - - if (newMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { - newMetaState |= AMETA_ALT_ON; - } - - if (newMetaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { - newMetaState |= AMETA_SHIFT_ON; - } - - return newMetaState; -} - static const int32_t keyCodeRotationMap[][4] = { // key codes enumerated counter-clockwise with the original (unrotated) key first // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation @@ -841,6 +805,17 @@ KeyboardInputMapper::~KeyboardInputMapper() { void KeyboardInputMapper::initializeLocked() { mLocked.metaState = AMETA_NONE; mLocked.downTime = 0; + + initializeLedStateLocked(mLocked.capsLockLedState, LED_CAPSL); + initializeLedStateLocked(mLocked.numLockLedState, LED_NUML); + initializeLedStateLocked(mLocked.scrollLockLedState, LED_SCROLLL); + + updateLedStateLocked(true); +} + +void KeyboardInputMapper::initializeLedStateLocked(LockedState::LedState& ledState, int32_t led) { + ledState.avail = getEventHub()->hasLed(getDeviceId(), led); + ledState.on = false; } uint32_t KeyboardInputMapper::getSources() { @@ -923,8 +898,8 @@ void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. if (mAssociatedDisplayId >= 0) { int32_t orientation; - if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) { - return; + if (!getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) { + orientation = InputReaderPolicyInterface::ROTATION_0; } keyCode = rotateKeyCode(keyCode, orientation); @@ -965,6 +940,7 @@ void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, if (oldMetaState != newMetaState) { mLocked.metaState = newMetaState; metaStateChanged = true; + updateLedStateLocked(false); } downTime = mLocked.downTime; @@ -974,6 +950,9 @@ void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, getContext()->updateGlobalMetaState(); } + if (policyFlags & POLICY_FLAG_FUNCTION) { + newMetaState |= AMETA_FUNCTION_ON; + } getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime); @@ -1009,6 +988,26 @@ int32_t KeyboardInputMapper::getMetaState() { } // release lock } +void KeyboardInputMapper::updateLedStateLocked(bool reset) { + updateLedStateForModifierLocked(mLocked.capsLockLedState, LED_CAPSL, + AMETA_CAPS_LOCK_ON, reset); + updateLedStateForModifierLocked(mLocked.numLockLedState, LED_NUML, + AMETA_NUM_LOCK_ON, reset); + updateLedStateForModifierLocked(mLocked.scrollLockLedState, LED_SCROLLL, + AMETA_SCROLL_LOCK_ON, reset); +} + +void KeyboardInputMapper::updateLedStateForModifierLocked(LockedState::LedState& ledState, + int32_t led, int32_t modifier, bool reset) { + if (ledState.avail) { + bool desiredState = (mLocked.metaState & modifier) != 0; + if (ledState.on != desiredState) { + getEventHub()->setLedState(getDeviceId(), led, desiredState); + ledState.on = desiredState; + } + } +} + // --- TrackballInputMapper --- @@ -1161,7 +1160,7 @@ void TrackballInputMapper::sync(nsecs_t when) { // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. int32_t orientation; if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) { - return; + orientation = InputReaderPolicyInterface::ROTATION_0; } float temp; @@ -1276,7 +1275,7 @@ void TouchInputMapper::dump(String8& dump) { dumpRawAxes(dump); dumpCalibration(dump); dumpSurfaceLocked(dump); - dump.appendFormat(INDENT3 "Translation and Scaling Factors:"); + dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n"); dump.appendFormat(INDENT4 "XOrigin: %d\n", mLocked.xOrigin); dump.appendFormat(INDENT4 "YOrigin: %d\n", mLocked.yOrigin); dump.appendFormat(INDENT4 "XScale: %0.3f\n", mLocked.xScale); @@ -1413,10 +1412,18 @@ bool TouchInputMapper::configureSurfaceLocked() { // Configure X and Y factors. if (mRawAxes.x.valid && mRawAxes.y.valid) { - mLocked.xOrigin = mRawAxes.x.minValue; - mLocked.yOrigin = mRawAxes.y.minValue; - mLocked.xScale = float(width) / mRawAxes.x.getRange(); - mLocked.yScale = float(height) / mRawAxes.y.getRange(); + mLocked.xOrigin = mCalibration.haveXOrigin + ? mCalibration.xOrigin + : mRawAxes.x.minValue; + mLocked.yOrigin = mCalibration.haveYOrigin + ? mCalibration.yOrigin + : mRawAxes.y.minValue; + mLocked.xScale = mCalibration.haveXScale + ? mCalibration.xScale + : float(width) / mRawAxes.x.getRange(); + mLocked.yScale = mCalibration.haveYScale + ? mCalibration.yScale + : float(height) / mRawAxes.y.getRange(); mLocked.xPrecision = 1.0f / mLocked.xScale; mLocked.yPrecision = 1.0f / mLocked.yScale; @@ -1682,6 +1689,12 @@ void TouchInputMapper::parseCalibration() { const InputDeviceCalibration& in = getDevice()->getCalibration(); Calibration& out = mCalibration; + // Position + out.haveXOrigin = in.tryGetProperty(String8("touch.position.xOrigin"), out.xOrigin); + out.haveYOrigin = in.tryGetProperty(String8("touch.position.yOrigin"), out.yOrigin); + out.haveXScale = in.tryGetProperty(String8("touch.position.xScale"), out.xScale); + out.haveYScale = in.tryGetProperty(String8("touch.position.yScale"), out.yScale); + // Touch Size out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_DEFAULT; String8 touchSizeCalibrationString; @@ -1891,6 +1904,20 @@ void TouchInputMapper::resolveCalibration() { void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT3 "Calibration:\n"); + // Position + if (mCalibration.haveXOrigin) { + dump.appendFormat(INDENT4 "touch.position.xOrigin: %d\n", mCalibration.xOrigin); + } + if (mCalibration.haveYOrigin) { + dump.appendFormat(INDENT4 "touch.position.yOrigin: %d\n", mCalibration.yOrigin); + } + if (mCalibration.haveXScale) { + dump.appendFormat(INDENT4 "touch.position.xScale: %0.3f\n", mCalibration.xScale); + } + if (mCalibration.haveYScale) { + dump.appendFormat(INDENT4 "touch.position.yScale: %0.3f\n", mCalibration.yScale); + } + // Touch Size switch (mCalibration.touchSizeCalibration) { case Calibration::TOUCH_SIZE_CALIBRATION_NONE: diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp index 2c6346e..1885691 100644 --- a/libs/ui/InputTransport.cpp +++ b/libs/ui/InputTransport.cpp @@ -35,8 +35,12 @@ static const int DEFAULT_MESSAGE_BUFFER_SIZE = 16384; static const char INPUT_SIGNAL_DISPATCH = 'D'; // Signal sent by the consumer to the producer to inform it that it has finished -// consuming the most recent message. -static const char INPUT_SIGNAL_FINISHED = 'f'; +// consuming the most recent message and it handled it. +static const char INPUT_SIGNAL_FINISHED_HANDLED = 'f'; + +// Signal sent by the consumer to the producer to inform it that it has finished +// consuming the most recent message but it did not handle it. +static const char INPUT_SIGNAL_FINISHED_UNHANDLED = 'u'; // --- InputChannel --- @@ -497,7 +501,7 @@ status_t InputPublisher::sendDispatchSignal() { return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH); } -status_t InputPublisher::receiveFinishedSignal() { +status_t InputPublisher::receiveFinishedSignal(bool& outHandled) { #if DEBUG_TRANSPORT_ACTIONS LOGD("channel '%s' publisher ~ receiveFinishedSignal", mChannel->getName().string()); @@ -506,9 +510,14 @@ status_t InputPublisher::receiveFinishedSignal() { char signal; status_t result = mChannel->receiveSignal(& signal); if (result) { + outHandled = false; return result; } - if (signal != INPUT_SIGNAL_FINISHED) { + if (signal == INPUT_SIGNAL_FINISHED_HANDLED) { + outHandled = true; + } else if (signal == INPUT_SIGNAL_FINISHED_UNHANDLED) { + outHandled = false; + } else { LOGE("channel '%s' publisher ~ Received unexpected signal '%c' from consumer", mChannel->getName().string(), signal); return UNKNOWN_ERROR; @@ -626,13 +635,15 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent* return OK; } -status_t InputConsumer::sendFinishedSignal() { +status_t InputConsumer::sendFinishedSignal(bool handled) { #if DEBUG_TRANSPORT_ACTIONS - LOGD("channel '%s' consumer ~ sendFinishedSignal", - mChannel->getName().string()); + LOGD("channel '%s' consumer ~ sendFinishedSignal: handled=%d", + mChannel->getName().string(), handled); #endif - return mChannel->sendSignal(INPUT_SIGNAL_FINISHED); + return mChannel->sendSignal(handled + ? INPUT_SIGNAL_FINISHED_HANDLED + : INPUT_SIGNAL_FINISHED_UNHANDLED); } status_t InputConsumer::receiveDispatchSignal() { diff --git a/libs/ui/KeyCharacterMap.cpp b/libs/ui/KeyCharacterMap.cpp index e891181..890cc3f 100644 --- a/libs/ui/KeyCharacterMap.cpp +++ b/libs/ui/KeyCharacterMap.cpp @@ -1,263 +1,807 @@ -#define LOG_TAG "KeyCharacterMap" +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -#include <ui/KeyCharacterMap.h> -#include <cutils/properties.h> +#define LOG_TAG "KeyCharacterMap" -#include <utils/Log.h> -#include <sys/types.h> -#include <unistd.h> #include <stdlib.h> -#include <fcntl.h> -#include <limits.h> #include <string.h> +#include <android/keycodes.h> +#include <ui/Keyboard.h> +#include <ui/KeyCharacterMap.h> +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/Tokenizer.h> +#include <utils/Timers.h> + +// Enables debug output for the parser. +#define DEBUG_PARSER 0 + +// Enables debug output for parser performance. +#define DEBUG_PARSER_PERFORMANCE 0 + +// Enables debug output for mapping. +#define DEBUG_MAPPING 0 + + +namespace android { -struct Header -{ - char magic[8]; - unsigned int endian; - unsigned int version; - unsigned int keycount; - unsigned char kbdtype; - char padding[11]; +static const char* WHITESPACE = " \t\r"; +static const char* WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r,:"; + +struct Modifier { + const char* label; + int32_t metaState; +}; +static const Modifier modifiers[] = { + { "shift", AMETA_SHIFT_ON }, + { "lshift", AMETA_SHIFT_LEFT_ON }, + { "rshift", AMETA_SHIFT_RIGHT_ON }, + { "alt", AMETA_ALT_ON }, + { "lalt", AMETA_ALT_LEFT_ON }, + { "ralt", AMETA_ALT_RIGHT_ON }, + { "ctrl", AMETA_CTRL_ON }, + { "lctrl", AMETA_CTRL_LEFT_ON }, + { "rctrl", AMETA_CTRL_RIGHT_ON }, + { "meta", AMETA_META_ON }, + { "lmeta", AMETA_META_LEFT_ON }, + { "rmeta", AMETA_META_RIGHT_ON }, + { "sym", AMETA_SYM_ON }, + { "fn", AMETA_FUNCTION_ON }, + { "capslock", AMETA_CAPS_LOCK_ON }, + { "numlock", AMETA_NUM_LOCK_ON }, + { "scrolllock", AMETA_SCROLL_LOCK_ON }, }; -KeyCharacterMap::KeyCharacterMap() -{ +#if DEBUG_MAPPING +static String8 toString(const char16_t* chars, size_t numChars) { + String8 result; + for (size_t i = 0; i < numChars; i++) { + result.appendFormat(i == 0 ? "%d" : ", %d", chars[i]); + } + return result; } +#endif + + +// --- KeyCharacterMap --- -KeyCharacterMap::~KeyCharacterMap() -{ - free(m_keys); +KeyCharacterMap::KeyCharacterMap() : + mType(KEYBOARD_TYPE_UNKNOWN) { } -unsigned short -KeyCharacterMap::get(int keycode, int meta) -{ - Key* k = find_key(keycode); - if (k != NULL) { - return k->data[meta & META_MASK]; +KeyCharacterMap::~KeyCharacterMap() { + for (size_t i = 0; i < mKeys.size(); i++) { + Key* key = mKeys.editValueAt(i); + delete key; } - return 0; } -unsigned short -KeyCharacterMap::getNumber(int keycode) -{ - Key* k = find_key(keycode); - if (k != NULL) { - return k->number; +status_t KeyCharacterMap::load(const String8& filename, KeyCharacterMap** outMap) { + *outMap = NULL; + + Tokenizer* tokenizer; + status_t status = Tokenizer::open(filename, &tokenizer); + if (status) { + LOGE("Error %d opening key character map file %s.", status, filename.string()); + } else { + KeyCharacterMap* map = new KeyCharacterMap(); + if (!map) { + LOGE("Error allocating key character map."); + status = NO_MEMORY; + } else { +#if DEBUG_PARSER_PERFORMANCE + nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); +#endif + Parser parser(map, tokenizer); + status = parser.parse(); +#if DEBUG_PARSER_PERFORMANCE + nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; + LOGD("Parsed key character map file '%s' %d lines in %0.3fms.", + tokenizer->getFilename().string(), tokenizer->getLineNumber(), + elapsedTime / 1000000.0); +#endif + if (status) { + delete map; + } else { + *outMap = map; + } + } + delete tokenizer; } - return 0; + return status; } -unsigned short -KeyCharacterMap::getMatch(int keycode, const unsigned short* chars, - int charsize, uint32_t modifiers) -{ - Key* k = find_key(keycode); - modifiers &= 3; // ignore the SYM key because we don't have keymap entries for it - if (k != NULL) { - const uint16_t* data = k->data; - for (int j=0; j<charsize; j++) { - uint16_t c = chars[j]; - for (int i=0; i<(META_MASK + 1); i++) { - if ((modifiers == 0) || ((modifiers & i) != 0)) { - if (c == data[i]) { - return c; +status_t KeyCharacterMap::loadByDeviceId(int32_t deviceId, KeyCharacterMap** outMap) { + *outMap = NULL; + + String8 filename; + status_t result = getKeyCharacterMapFile(deviceId, filename); + if (!result) { + result = load(filename, outMap); + } + return result; +} + +int32_t KeyCharacterMap::getKeyboardType() const { + return mType; +} + +char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const { + char16_t result = 0; + ssize_t index = mKeys.indexOfKey(keyCode); + if (index >= 0) { + const Key* key = mKeys.valueAt(index); + result = key->label; + } +#if DEBUG_MAPPING + LOGD("getDisplayLabel: keyCode=%d ~ Result %d.", keyCode, result); +#endif + return result; +} + +char16_t KeyCharacterMap::getNumber(int32_t keyCode) const { + char16_t result = 0; + ssize_t index = mKeys.indexOfKey(keyCode); + if (index >= 0) { + const Key* key = mKeys.valueAt(index); + result = key->number; + } +#if DEBUG_MAPPING + LOGD("getNumber: keyCode=%d ~ Result %d.", keyCode, result); +#endif + return result; +} + +char16_t KeyCharacterMap::getCharacter(int32_t keyCode, int32_t metaState) const { + char16_t result = 0; + ssize_t index = mKeys.indexOfKey(keyCode); + if (index >= 0) { + const Key* key = mKeys.valueAt(index); + for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) { + if ((behavior->metaState & metaState) == behavior->metaState) { + result = behavior->character; + break; + } + } + } +#if DEBUG_MAPPING + LOGD("getCharacter: keyCode=%d, metaState=0x%08x ~ Result %d.", keyCode, metaState, result); +#endif + return result; +} + +char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars, + int32_t metaState) const { + char16_t result = 0; + ssize_t index = mKeys.indexOfKey(keyCode); + if (index >= 0) { + const Key* key = mKeys.valueAt(index); + + // Try to find the most general behavior that maps to this character. + // For example, the base key behavior will usually be last in the list. + // However, if we find a perfect meta state match for one behavior then use that one. + for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) { + if (behavior->character) { + for (size_t i = 0; i < numChars; i++) { + if (behavior->character == chars[i]) { + result = behavior->character; + if ((behavior->metaState & metaState) == behavior->metaState) { + goto ExactMatch; + } + break; } } } } + ExactMatch: ; } - return 0; +#if DEBUG_MAPPING + LOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.", + keyCode, toString(chars, numChars).string(), metaState, result); +#endif + return result; } -unsigned short -KeyCharacterMap::getDisplayLabel(int keycode) -{ - Key* k = find_key(keycode); - if (k != NULL) { - return k->display_label; +bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t numChars, + Vector<KeyEvent>& outEvents) const { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + + for (size_t i = 0; i < numChars; i++) { + int32_t keyCode, metaState; + char16_t ch = chars[i]; + if (!findKey(ch, &keyCode, &metaState)) { +#if DEBUG_MAPPING + LOGD("getEvents: deviceId=%d, chars=[%s] ~ Failed to find mapping for character %d.", + deviceId, toString(chars, numChars).string(), ch); +#endif + return false; + } + + int32_t currentMetaState = 0; + addMetaKeys(outEvents, deviceId, metaState, true, now, ¤tMetaState); + addKey(outEvents, deviceId, keyCode, currentMetaState, true, now); + addKey(outEvents, deviceId, keyCode, currentMetaState, false, now); + addMetaKeys(outEvents, deviceId, metaState, false, now, ¤tMetaState); + } +#if DEBUG_MAPPING + LOGD("getEvents: deviceId=%d, chars=[%s] ~ Generated %d events.", + deviceId, toString(chars, numChars).string(), outEvents.size()); + for (size_t i = 0; i < outEvents.size(); i++) { + LOGD(" Key: keyCode=%d, metaState=0x%08x, %s.", + outEvents[i].getKeyCode(), outEvents[i].getMetaState(), + outEvents[i].getAction() == AKEY_EVENT_ACTION_DOWN ? "down" : "up"); } - return 0; +#endif + return true; } -bool -KeyCharacterMap::getKeyData(int keycode, unsigned short *displayLabel, - unsigned short *number, unsigned short* results) -{ - Key* k = find_key(keycode); - if (k != NULL) { - memcpy(results, k->data, sizeof(short)*(META_MASK + 1)); - *number = k->number; - *displayLabel = k->display_label; - return true; - } else { +bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const { + if (!ch) { return false; } -} -bool -KeyCharacterMap::find_char(uint16_t c, uint32_t* key, uint32_t* mods) -{ - uint32_t N = m_keyCount; - for (int j=0; j<(META_MASK + 1); j++) { - Key const* keys = m_keys; - for (uint32_t i=0; i<N; i++) { - if (keys->data[j] == c) { - *key = keys->keycode; - *mods = j; - return true; + for (size_t i = 0; i < mKeys.size(); i++) { + const Key* key = mKeys.valueAt(i); + + // Try to find the most general behavior that maps to this character. + // For example, the base key behavior will usually be last in the list. + const Behavior* found = NULL; + for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) { + if (behavior->character == ch) { + found = behavior; } - keys++; + } + if (found) { + *outKeyCode = mKeys.keyAt(i); + *outMetaState = found->metaState; + return true; } } return false; } -bool -KeyCharacterMap::getEvents(uint16_t* chars, size_t len, - Vector<int32_t>* keys, Vector<uint32_t>* modifiers) -{ - for (size_t i=0; i<len; i++) { - uint32_t k, mods; - if (find_char(chars[i], &k, &mods)) { - keys->add(k); - modifiers->add(mods); - } else { - return false; - } +void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents, + int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) { + outEvents.push(); + KeyEvent& event = outEvents.editTop(); + event.initialize(deviceId, AINPUT_SOURCE_KEYBOARD, + down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, + 0, keyCode, 0, metaState, 0, time, time); +} + +void KeyCharacterMap::addMetaKeys(Vector<KeyEvent>& outEvents, + int32_t deviceId, int32_t metaState, bool down, nsecs_t time, + int32_t* currentMetaState) { + // Add and remove meta keys symmetrically. + if (down) { + addLockedMetaKey(outEvents, deviceId, metaState, time, + AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState); + addLockedMetaKey(outEvents, deviceId, metaState, time, + AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState); + addLockedMetaKey(outEvents, deviceId, metaState, time, + AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState); + + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, + AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON, + AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON, + AMETA_SHIFT_ON, currentMetaState); + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, + AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON, + AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON, + AMETA_ALT_ON, currentMetaState); + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, + AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON, + AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON, + AMETA_CTRL_ON, currentMetaState); + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, + AKEYCODE_META_LEFT, AMETA_META_LEFT_ON, + AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON, + AMETA_META_ON, currentMetaState); + + addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, + AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState); + addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, + AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState); + } else { + addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, + AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState); + addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, + AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState); + + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, + AKEYCODE_META_LEFT, AMETA_META_LEFT_ON, + AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON, + AMETA_META_ON, currentMetaState); + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, + AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON, + AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON, + AMETA_CTRL_ON, currentMetaState); + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, + AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON, + AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON, + AMETA_ALT_ON, currentMetaState); + addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, + AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON, + AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON, + AMETA_SHIFT_ON, currentMetaState); + + addLockedMetaKey(outEvents, deviceId, metaState, time, + AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState); + addLockedMetaKey(outEvents, deviceId, metaState, time, + AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState); + addLockedMetaKey(outEvents, deviceId, metaState, time, + AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState); } - return true; } -KeyCharacterMap::Key* -KeyCharacterMap::find_key(int keycode) -{ - Key* keys = m_keys; - int low = 0; - int high = m_keyCount - 1; - int mid; - int n; - while (low <= high) { - mid = (low + high) / 2; - n = keys[mid].keycode; - if (keycode < n) { - high = mid - 1; - } else if (keycode > n) { - low = mid + 1; - } else { - return keys + mid; - } +bool KeyCharacterMap::addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents, + int32_t deviceId, int32_t metaState, bool down, nsecs_t time, + int32_t keyCode, int32_t keyMetaState, + int32_t* currentMetaState) { + if ((metaState & keyMetaState) == keyMetaState) { + *currentMetaState = updateMetaState(keyCode, down, *currentMetaState); + addKey(outEvents, deviceId, keyCode, *currentMetaState, down, time); + return true; + } + return false; +} + +void KeyCharacterMap::addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents, + int32_t deviceId, int32_t metaState, bool down, nsecs_t time, + int32_t leftKeyCode, int32_t leftKeyMetaState, + int32_t rightKeyCode, int32_t rightKeyMetaState, + int32_t eitherKeyMetaState, + int32_t* currentMetaState) { + bool specific = false; + specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time, + leftKeyCode, leftKeyMetaState, currentMetaState); + specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time, + rightKeyCode, rightKeyMetaState, currentMetaState); + + if (!specific) { + addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time, + leftKeyCode, eitherKeyMetaState, currentMetaState); + } +} + +void KeyCharacterMap::addLockedMetaKey(Vector<KeyEvent>& outEvents, + int32_t deviceId, int32_t metaState, nsecs_t time, + int32_t keyCode, int32_t keyMetaState, + int32_t* currentMetaState) { + if ((metaState & keyMetaState) == keyMetaState) { + *currentMetaState = updateMetaState(keyCode, true, *currentMetaState); + addKey(outEvents, deviceId, keyCode, *currentMetaState, true, time); + *currentMetaState = updateMetaState(keyCode, false, *currentMetaState); + addKey(outEvents, deviceId, keyCode, *currentMetaState, false, time); + } +} + + +// --- KeyCharacterMap::Key --- + +KeyCharacterMap::Key::Key() : + label(0), number(0), firstBehavior(NULL) { +} + +KeyCharacterMap::Key::~Key() { + Behavior* behavior = firstBehavior; + while (behavior) { + Behavior* next = behavior->next; + delete behavior; + behavior = next; } - return NULL; } -KeyCharacterMap* -KeyCharacterMap::load(int id) -{ - KeyCharacterMap* rv = NULL; - char path[PATH_MAX]; - char propName[100]; - char dev[PROPERTY_VALUE_MAX]; - char tmpfn[PROPERTY_VALUE_MAX]; - int err; - const char* root = getenv("ANDROID_ROOT"); - - sprintf(propName, "hw.keyboards.%u.devname", id); - err = property_get(propName, dev, ""); - if (err > 0) { - // replace all the spaces with underscores - strcpy(tmpfn, dev); - for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' ')) - *p = '_'; - snprintf(path, sizeof(path), "%s/usr/keychars/%s.kcm.bin", root, tmpfn); - //LOGD("load: dev='%s' path='%s'\n", dev, path); - rv = try_file(path); - if (rv != NULL) { - return rv; + +// --- KeyCharacterMap::Behavior --- + +KeyCharacterMap::Behavior::Behavior() : + next(NULL), metaState(0), character(0), fallbackKeyCode(0) { +} + + +// --- KeyCharacterMap::Parser --- + +KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer) : + mMap(map), mTokenizer(tokenizer), mState(STATE_TOP) { +} + +KeyCharacterMap::Parser::~Parser() { +} + +status_t KeyCharacterMap::Parser::parse() { + while (!mTokenizer->isEof()) { +#if DEBUG_PARSER + LOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(), + mTokenizer->peekRemainderOfLine().string()); +#endif + + mTokenizer->skipDelimiters(WHITESPACE); + + if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { + switch (mState) { + case STATE_TOP: { + String8 keywordToken = mTokenizer->nextToken(WHITESPACE); + if (keywordToken == "type") { + mTokenizer->skipDelimiters(WHITESPACE); + status_t status = parseType(); + if (status) return status; + } else if (keywordToken == "key") { + mTokenizer->skipDelimiters(WHITESPACE); + status_t status = parseKey(); + if (status) return status; + } else { + LOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(), + keywordToken.string()); + return BAD_VALUE; + } + break; + } + + case STATE_KEY: { + status_t status = parseKeyProperty(); + if (status) return status; + break; + } + } + + mTokenizer->skipDelimiters(WHITESPACE); + if (!mTokenizer->isEol()) { + LOGE("%s: Expected end of line, got '%s'.", + mTokenizer->getLocation().string(), + mTokenizer->peekRemainderOfLine().string()); + return BAD_VALUE; + } } - LOGW("Error loading keycharmap file '%s'. %s='%s'", path, propName, dev); - } else { - LOGW("No keyboard for id %d", id); + + mTokenizer->nextLine(); + } + + if (mState != STATE_TOP) { + LOGE("%s: Unterminated key description at end of file.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + + if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) { + LOGE("%s: Missing required keyboard 'type' declaration.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + + return NO_ERROR; +} + +status_t KeyCharacterMap::Parser::parseType() { + if (mMap->mType != KEYBOARD_TYPE_UNKNOWN) { + LOGE("%s: Duplicate keyboard 'type' declaration.", + mTokenizer->getLocation().string()); + return BAD_VALUE; } - snprintf(path, sizeof(path), "%s/usr/keychars/qwerty.kcm.bin", root); - rv = try_file(path); - if (rv == NULL) { - LOGE("Can't find any keycharmaps (also tried %s)", path); - return NULL; + KeyboardType type; + String8 typeToken = mTokenizer->nextToken(WHITESPACE); + if (typeToken == "NUMERIC") { + type = KEYBOARD_TYPE_NUMERIC; + } else if (typeToken == "PREDICTIVE") { + type = KEYBOARD_TYPE_PREDICTIVE; + } else if (typeToken == "ALPHA") { + type = KEYBOARD_TYPE_ALPHA; + } else if (typeToken == "FULL") { + type = KEYBOARD_TYPE_FULL; + } else if (typeToken == "SPECIAL_FUNCTION") { + type = KEYBOARD_TYPE_SPECIAL_FUNCTION; + } else { + LOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().string(), + typeToken.string()); + return BAD_VALUE; } - LOGW("Using default keymap: %s", path); - return rv; +#if DEBUG_PARSER + LOGD("Parsed type: type=%d.", type); +#endif + mMap->mType = type; + return NO_ERROR; } -KeyCharacterMap* -KeyCharacterMap::try_file(const char* filename) -{ - KeyCharacterMap* rv = NULL; - Key* keys; - int fd; - off_t filesize; - Header header; - int err; - - fd = open(filename, O_RDONLY); - if (fd == -1) { - LOGW("Can't open keycharmap file"); - return NULL; +status_t KeyCharacterMap::Parser::parseKey() { + String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); + int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string()); + if (!keyCode) { + LOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), + keyCodeToken.string()); + return BAD_VALUE; + } + if (mMap->mKeys.indexOfKey(keyCode) >= 0) { + LOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(), + keyCodeToken.string()); + return BAD_VALUE; + } + + mTokenizer->skipDelimiters(WHITESPACE); + String8 openBraceToken = mTokenizer->nextToken(WHITESPACE); + if (openBraceToken != "{") { + LOGE("%s: Expected '{' after key code label, got '%s'.", + mTokenizer->getLocation().string(), openBraceToken.string()); + return BAD_VALUE; } - filesize = lseek(fd, 0, SEEK_END); - lseek(fd, 0, SEEK_SET); +#if DEBUG_PARSER + LOGD("Parsed beginning of key: keyCode=%d.", keyCode); +#endif + mKeyCode = keyCode; + mMap->mKeys.add(keyCode, new Key()); + mState = STATE_KEY; + return NO_ERROR; +} - // validate the header - if (filesize <= (off_t)sizeof(header)) { - LOGW("Bad keycharmap - filesize=%d\n", (int)filesize); - goto cleanup1; +status_t KeyCharacterMap::Parser::parseKeyProperty() { + String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); + if (token == "}") { + mState = STATE_TOP; + return NO_ERROR; } - err = read(fd, &header, sizeof(header)); - if (err == -1) { - LOGW("Error reading keycharmap file"); - goto cleanup1; + Vector<Property> properties; + + // Parse all comma-delimited property names up to the first colon. + for (;;) { + if (token == "label") { + properties.add(Property(PROPERTY_LABEL)); + } else if (token == "number") { + properties.add(Property(PROPERTY_NUMBER)); + } else { + int32_t metaState; + status_t status = parseModifier(token, &metaState); + if (status) { + LOGE("%s: Expected a property name or modifier, got '%s'.", + mTokenizer->getLocation().string(), token.string()); + return status; + } + properties.add(Property(PROPERTY_META, metaState)); + } + + mTokenizer->skipDelimiters(WHITESPACE); + if (!mTokenizer->isEol()) { + char ch = mTokenizer->nextChar(); + if (ch == ':') { + break; + } else if (ch == ',') { + mTokenizer->skipDelimiters(WHITESPACE); + token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); + continue; + } + } + + LOGE("%s: Expected ',' or ':' after property name.", + mTokenizer->getLocation().string()); + return BAD_VALUE; } - if (0 != memcmp(header.magic, "keychar", 8)) { - LOGW("Bad keycharmap magic token"); - goto cleanup1; + // Parse behavior after the colon. + mTokenizer->skipDelimiters(WHITESPACE); + + Behavior behavior; + bool haveCharacter = false; + bool haveFallback = false; + + do { + char ch = mTokenizer->peekChar(); + if (ch == '\'') { + char16_t character; + status_t status = parseCharacterLiteral(&character); + if (status || !character) { + LOGE("%s: Invalid character literal for key.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + if (haveCharacter) { + LOGE("%s: Cannot combine multiple character literals or 'none'.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + behavior.character = character; + haveCharacter = true; + } else { + token = mTokenizer->nextToken(WHITESPACE); + if (token == "none") { + if (haveCharacter) { + LOGE("%s: Cannot combine multiple character literals or 'none'.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + haveCharacter = true; + } else if (token == "fallback") { + mTokenizer->skipDelimiters(WHITESPACE); + token = mTokenizer->nextToken(WHITESPACE); + int32_t keyCode = getKeyCodeByLabel(token.string()); + if (!keyCode) { + LOGE("%s: Invalid key code label for fallback behavior, got '%s'.", + mTokenizer->getLocation().string(), + token.string()); + return BAD_VALUE; + } + if (haveFallback) { + LOGE("%s: Cannot combine multiple fallback key codes.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + behavior.fallbackKeyCode = keyCode; + haveFallback = true; + } else { + LOGE("%s: Expected a key behavior after ':'.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + } + + mTokenizer->skipDelimiters(WHITESPACE); + } while (!mTokenizer->isEol()); + + // Add the behavior. + Key* key = mMap->mKeys.valueFor(mKeyCode); + for (size_t i = 0; i < properties.size(); i++) { + const Property& property = properties.itemAt(i); + switch (property.property) { + case PROPERTY_LABEL: + if (key->label) { + LOGE("%s: Duplicate label for key.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + key->label = behavior.character; +#if DEBUG_PARSER + LOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label); +#endif + break; + case PROPERTY_NUMBER: + if (key->number) { + LOGE("%s: Duplicate number for key.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + key->number = behavior.character; +#if DEBUG_PARSER + LOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number); +#endif + break; + case PROPERTY_META: { + for (Behavior* b = key->firstBehavior; b; b = b->next) { + if (b->metaState == property.metaState) { + LOGE("%s: Duplicate key behavior for modifier.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + } + Behavior* newBehavior = new Behavior(behavior); + newBehavior->metaState = property.metaState; + newBehavior->next = key->firstBehavior; + key->firstBehavior = newBehavior; +#if DEBUG_PARSER + LOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d.", mKeyCode, + newBehavior->metaState, newBehavior->character, newBehavior->fallbackKeyCode); +#endif + break; + } + } } - if (header.endian != 0x12345678) { - LOGW("Bad keycharmap endians"); - goto cleanup1; + return NO_ERROR; +} + +status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* outMetaState) { + if (token == "base") { + *outMetaState = 0; + return NO_ERROR; } - if ((header.version & 0xff) != 2) { - LOGW("Only support keycharmap version 2 (got 0x%08x)", header.version); - goto cleanup1; + + int32_t combinedMeta = 0; + + const char* str = token.string(); + const char* start = str; + for (const char* cur = str; ; cur++) { + char ch = *cur; + if (ch == '+' || ch == '\0') { + size_t len = cur - start; + int32_t metaState = 0; + for (size_t i = 0; i < sizeof(modifiers) / sizeof(Modifier); i++) { + if (strlen(modifiers[i].label) == len + && strncmp(modifiers[i].label, start, len) == 0) { + metaState = modifiers[i].metaState; + break; + } + } + if (!metaState) { + return BAD_VALUE; + } + if (combinedMeta & metaState) { + LOGE("%s: Duplicate modifier combination '%s'.", + mTokenizer->getLocation().string(), token.string()); + return BAD_VALUE; + } + + combinedMeta |= metaState; + + if (ch == '\0') { + break; + } + } } - if (filesize < (off_t)(sizeof(Header)+(sizeof(Key)*header.keycount))) { - LOGW("Bad keycharmap file size\n"); - goto cleanup1; + *outMetaState = combinedMeta; + return NO_ERROR; +} + +status_t KeyCharacterMap::Parser::parseCharacterLiteral(char16_t* outCharacter) { + char ch = mTokenizer->nextChar(); + if (ch != '\'') { + goto Error; } - // read the key data - keys = (Key*)malloc(sizeof(Key)*header.keycount); - err = read(fd, keys, sizeof(Key)*header.keycount); - if (err == -1) { - LOGW("Error reading keycharmap file"); - free(keys); - goto cleanup1; + ch = mTokenizer->nextChar(); + if (ch == '\\') { + // Escape sequence. + ch = mTokenizer->nextChar(); + if (ch == 'n') { + *outCharacter = '\n'; + } else if (ch == 't') { + *outCharacter = '\t'; + } else if (ch == '\\') { + *outCharacter = '\\'; + } else if (ch == '\'') { + *outCharacter = '\''; + } else if (ch == '"') { + *outCharacter = '"'; + } else if (ch == 'u') { + *outCharacter = 0; + for (int i = 0; i < 4; i++) { + ch = mTokenizer->nextChar(); + int digit; + if (ch >= '0' && ch <= '9') { + digit = ch - '0'; + } else if (ch >= 'A' && ch <= 'F') { + digit = ch - 'A' + 10; + } else if (ch >= 'a' && ch <= 'f') { + digit = ch - 'a' + 10; + } else { + goto Error; + } + *outCharacter = (*outCharacter << 4) | digit; + } + } else { + goto Error; + } + } else if (ch >= 32 && ch <= 126 && ch != '\'') { + // ASCII literal character. + *outCharacter = ch; + } else { + goto Error; } - // return the object - rv = new KeyCharacterMap; - rv->m_keyCount = header.keycount; - rv->m_keys = keys; - rv->m_type = header.kbdtype; + ch = mTokenizer->nextChar(); + if (ch != '\'') { + goto Error; + } -cleanup1: - close(fd); + // Ensure that we consumed the entire token. + if (mTokenizer->nextToken(WHITESPACE).isEmpty()) { + return NO_ERROR; + } - return rv; +Error: + LOGE("%s: Malformed character literal.", mTokenizer->getLocation().string()); + return BAD_VALUE; } + +} // namespace android diff --git a/libs/ui/KeyLayoutMap.cpp b/libs/ui/KeyLayoutMap.cpp index 15ae54c..56bc26f 100644 --- a/libs/ui/KeyLayoutMap.cpp +++ b/libs/ui/KeyLayoutMap.cpp @@ -1,234 +1,213 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #define LOG_TAG "KeyLayoutMap" -#include "KeyLayoutMap.h" -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> -#include <errno.h> -#include <utils/String8.h> #include <stdlib.h> -#include <ui/KeycodeLabels.h> +#include <android/keycodes.h> +#include <ui/Keyboard.h> +#include <ui/KeyLayoutMap.h> #include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/Tokenizer.h> +#include <utils/Timers.h> + +// Enables debug output for the parser. +#define DEBUG_PARSER 0 + +// Enables debug output for parser performance. +#define DEBUG_PARSER_PERFORMANCE 0 + +// Enables debug output for mapping. +#define DEBUG_MAPPING 0 + namespace android { -KeyLayoutMap::KeyLayoutMap() - :m_status(NO_INIT), - m_keys() -{ -} +static const char* WHITESPACE = " \t\r"; -KeyLayoutMap::~KeyLayoutMap() -{ +// --- KeyLayoutMap --- + +KeyLayoutMap::KeyLayoutMap() { } -static String8 -next_token(char const** p, int *line) -{ - bool begun = false; - const char* begin = *p; - const char* end = *p; - while (true) { - if (*end == '\n') { - (*line)++; - } - switch (*end) - { - case '#': - if (begun) { - *p = end; - return String8(begin, end-begin); - } else { - do { - begin++; - end++; - } while (*begin != '\0' && *begin != '\n'); - } - case '\0': - case ' ': - case '\n': - case '\r': - case '\t': - if (begun || (*end == '\0')) { - *p = end; - return String8(begin, end-begin); - } else { - begin++; - end++; - break; - } - default: - end++; - begun = true; - } - } +KeyLayoutMap::~KeyLayoutMap() { } -static int32_t -token_to_value(const char *literal, const KeycodeLabel *list) -{ - while (list->literal) { - if (0 == strcmp(literal, list->literal)) { - return list->value; +status_t KeyLayoutMap::load(const String8& filename, KeyLayoutMap** outMap) { + *outMap = NULL; + + Tokenizer* tokenizer; + status_t status = Tokenizer::open(filename, &tokenizer); + if (status) { + LOGE("Error %d opening key layout map file %s.", status, filename.string()); + } else { + KeyLayoutMap* map = new KeyLayoutMap(); + if (!map) { + LOGE("Error allocating key layout map."); + status = NO_MEMORY; + } else { +#if DEBUG_PARSER_PERFORMANCE + nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); +#endif + Parser parser(map, tokenizer); + status = parser.parse(); +#if DEBUG_PARSER_PERFORMANCE + nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; + LOGD("Parsed key layout map file '%s' %d lines in %0.3fms.", + tokenizer->getFilename().string(), tokenizer->getLineNumber(), + elapsedTime / 1000000.0); +#endif + if (status) { + delete map; + } else { + *outMap = map; + } } - list++; + delete tokenizer; } - return list->value; + return status; } -status_t -KeyLayoutMap::load(const char* filename) -{ - int fd = open(filename, O_RDONLY); - if (fd < 0) { - LOGE("error opening file=%s err=%s\n", filename, strerror(errno)); - m_status = errno; - return errno; +status_t KeyLayoutMap::map(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const { + ssize_t index = mKeys.indexOfKey(scanCode); + if (index < 0) { +#if DEBUG_MAPPING + LOGD("map: scanCode=%d ~ Failed.", scanCode); +#endif + *keyCode = AKEYCODE_UNKNOWN; + *flags = 0; + return NAME_NOT_FOUND; } - off_t len = lseek(fd, 0, SEEK_END); - off_t errlen = lseek(fd, 0, SEEK_SET); - if (len < 0 || errlen < 0) { - close(fd); - LOGE("error seeking file=%s err=%s\n", filename, strerror(errno)); - m_status = errno; - return errno; - } + const Key& k = mKeys.valueAt(index); + *keyCode = k.keyCode; + *flags = k.flags; - char* buf = (char*)malloc(len+1); - if (read(fd, buf, len) != len) { - LOGE("error reading file=%s err=%s\n", filename, strerror(errno)); - m_status = errno != 0 ? errno : ((int)NOT_ENOUGH_DATA); - return errno != 0 ? errno : ((int)NOT_ENOUGH_DATA); - } - errno = 0; - buf[len] = '\0'; +#if DEBUG_MAPPING + LOGD("map: scanCode=%d ~ Result keyCode=%d, flags=0x%08x.", scanCode, *keyCode, *flags); +#endif + return NO_ERROR; +} - int32_t scancode = -1; - int32_t keycode = -1; - uint32_t flags = 0; - uint32_t tmp; - char* end; - status_t err = NO_ERROR; - int line = 1; - char const* p = buf; - enum { BEGIN, SCANCODE, KEYCODE, FLAG } state = BEGIN; - while (true) { - String8 token = next_token(&p, &line); - if (*p == '\0') { - break; - } - switch (state) - { - case BEGIN: - if (token == "key") { - state = SCANCODE; - } else { - LOGE("%s:%d: expected key, got '%s'\n", filename, line, - token.string()); - err = BAD_VALUE; - goto done; - } - break; - case SCANCODE: - scancode = strtol(token.string(), &end, 0); - if (*end != '\0') { - LOGE("%s:%d: expected scancode (a number), got '%s'\n", - filename, line, token.string()); - goto done; - } - //LOGI("%s:%d: got scancode %d\n", filename, line, scancode ); - state = KEYCODE; - break; - case KEYCODE: - keycode = token_to_value(token.string(), KEYCODES); - //LOGI("%s:%d: got keycode %d for %s\n", filename, line, keycode, token.string() ); - if (keycode == 0) { - LOGE("%s:%d: expected keycode, got '%s'\n", - filename, line, token.string()); - goto done; - } - state = FLAG; - break; - case FLAG: - if (token == "key") { - if (scancode != -1) { - //LOGI("got key decl scancode=%d keycode=%d" - // " flags=0x%08x\n", scancode, keycode, flags); - Key k = { keycode, flags }; - m_keys.add(scancode, k); - state = SCANCODE; - scancode = -1; - keycode = -1; - flags = 0; - break; - } - } - tmp = token_to_value(token.string(), FLAGS); - //LOGI("%s:%d: got flags %x for %s\n", filename, line, tmp, token.string() ); - if (tmp == 0) { - LOGE("%s:%d: expected flag, got '%s'\n", - filename, line, token.string()); - goto done; - } - flags |= tmp; - break; +status_t KeyLayoutMap::findScanCodes(int32_t keyCode, Vector<int32_t>* outScanCodes) const { + const size_t N = mKeys.size(); + for (size_t i=0; i<N; i++) { + if (mKeys.valueAt(i).keyCode == keyCode) { + outScanCodes->add(mKeys.keyAt(i)); } } - if (state == FLAG && scancode != -1 ) { - //LOGI("got key decl scancode=%d keycode=%d" - // " flags=0x%08x\n", scancode, keycode, flags); - Key k = { keycode, flags }; - m_keys.add(scancode, k); - } - -done: - free(buf); - close(fd); - - m_status = err; - return err; + return NO_ERROR; } -status_t -KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags) const -{ - if (m_status != NO_ERROR) { - return m_status; - } +// --- KeyLayoutMap::Parser --- - ssize_t index = m_keys.indexOfKey(scancode); - if (index < 0) { - //LOGW("couldn't map scancode=%d\n", scancode); - return NAME_NOT_FOUND; - } - - const Key& k = m_keys.valueAt(index); +KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) : + mMap(map), mTokenizer(tokenizer) { +} - *keycode = k.keycode; - *flags = k.flags; +KeyLayoutMap::Parser::~Parser() { +} - //LOGD("mapped scancode=%d to keycode=%d flags=0x%08x\n", scancode, - // keycode, flags); +status_t KeyLayoutMap::Parser::parse() { + while (!mTokenizer->isEof()) { +#if DEBUG_PARSER + LOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(), + mTokenizer->peekRemainderOfLine().string()); +#endif + + mTokenizer->skipDelimiters(WHITESPACE); + + if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { + String8 keywordToken = mTokenizer->nextToken(WHITESPACE); + if (keywordToken == "key") { + mTokenizer->skipDelimiters(WHITESPACE); + status_t status = parseKey(); + if (status) return status; + } else { + LOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(), + keywordToken.string()); + return BAD_VALUE; + } + + mTokenizer->skipDelimiters(WHITESPACE); + if (!mTokenizer->isEol()) { + LOGE("%s: Expected end of line, got '%s'.", + mTokenizer->getLocation().string(), + mTokenizer->peekRemainderOfLine().string()); + return BAD_VALUE; + } + } + mTokenizer->nextLine(); + } return NO_ERROR; } -status_t -KeyLayoutMap::findScancodes(int32_t keycode, Vector<int32_t>* outScancodes) const -{ - if (m_status != NO_ERROR) { - return m_status; +status_t KeyLayoutMap::Parser::parseKey() { + String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE); + char* end; + int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0)); + if (*end) { + LOGE("%s: Expected scan code number, got '%s'.", mTokenizer->getLocation().string(), + scanCodeToken.string()); + return BAD_VALUE; } - - const size_t N = m_keys.size(); - for (size_t i=0; i<N; i++) { - if (m_keys.valueAt(i).keycode == keycode) { - outScancodes->add(m_keys.keyAt(i)); + if (mMap->mKeys.indexOfKey(scanCode) >= 0) { + LOGE("%s: Duplicate entry for scan code '%s'.", mTokenizer->getLocation().string(), + scanCodeToken.string()); + return BAD_VALUE; + } + + mTokenizer->skipDelimiters(WHITESPACE); + String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); + int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string()); + if (!keyCode) { + LOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), + keyCodeToken.string()); + return BAD_VALUE; + } + + uint32_t flags = 0; + for (;;) { + mTokenizer->skipDelimiters(WHITESPACE); + if (mTokenizer->isEol()) break; + + String8 flagToken = mTokenizer->nextToken(WHITESPACE); + uint32_t flag = getKeyFlagByLabel(flagToken.string()); + if (!flag) { + LOGE("%s: Expected flag label, got '%s'.", mTokenizer->getLocation().string(), + flagToken.string()); + return BAD_VALUE; } + if (flags & flag) { + LOGE("%s: Duplicate flag '%s'.", mTokenizer->getLocation().string(), + flagToken.string()); + return BAD_VALUE; + } + flags |= flag; } - + +#if DEBUG_PARSER + LOGD("Parsed key: scanCode=%d, keyCode=%d, flags=0x%08x.", scanCode, keyCode, flags); +#endif + Key key; + key.keyCode = keyCode; + key.flags = flags; + mMap->mKeys.add(scanCode, key); return NO_ERROR; } diff --git a/libs/ui/KeyLayoutMap.h b/libs/ui/KeyLayoutMap.h deleted file mode 100644 index 43f84ce..0000000 --- a/libs/ui/KeyLayoutMap.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef KEYLAYOUTMAP_H -#define KEYLAYOUTMAP_H - -#include <utils/KeyedVector.h> - -namespace android { - -class KeyLayoutMap -{ -public: - KeyLayoutMap(); - ~KeyLayoutMap(); - - status_t load(const char* filename); - - status_t map(int32_t scancode, int32_t *keycode, uint32_t *flags) const; - status_t findScancodes(int32_t keycode, Vector<int32_t>* outScancodes) const; - -private: - struct Key { - int32_t keycode; - uint32_t flags; - }; - - status_t m_status; - KeyedVector<int32_t,Key> m_keys; -}; - -}; - -#endif // KEYLAYOUTMAP_H diff --git a/libs/ui/Keyboard.cpp b/libs/ui/Keyboard.cpp new file mode 100644 index 0000000..de76e25 --- /dev/null +++ b/libs/ui/Keyboard.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Keyboard" + +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> + +#include <ui/Keyboard.h> +#include <ui/KeycodeLabels.h> +#include <utils/Errors.h> +#include <utils/Log.h> +#include <cutils/properties.h> + +namespace android { + +static void selectKeyMap(KeyMapInfo& keyMapInfo, const String8& keyMapName, bool defaultKeyMap) { + if (keyMapInfo.keyMapName.isEmpty()) { + keyMapInfo.keyMapName.setTo(keyMapName); + keyMapInfo.isDefaultKeyMap = defaultKeyMap; + } +} + +static bool probeKeyMap(KeyMapInfo& keyMapInfo, 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 = !keyMapInfo.keyLayoutFile.isEmpty(); + if (!haveKeyLayout) { + keyMapInfo.keyLayoutFile.setTo(root); + keyMapInfo.keyLayoutFile.append("/usr/keylayout/"); + keyMapInfo.keyLayoutFile.append(keyMapName); + keyMapInfo.keyLayoutFile.append(".kl"); + if (access(keyMapInfo.keyLayoutFile.string(), R_OK)) { + keyMapInfo.keyLayoutFile.clear(); + } else { + haveKeyLayout = true; + } + } + + bool haveKeyCharacterMap = !keyMapInfo.keyCharacterMapFile.isEmpty(); + if (!haveKeyCharacterMap) { + keyMapInfo.keyCharacterMapFile.setTo(root); + keyMapInfo.keyCharacterMapFile.append("/usr/keychars/"); + keyMapInfo.keyCharacterMapFile.append(keyMapName); + keyMapInfo.keyCharacterMapFile.append(".kcm"); + if (access(keyMapInfo.keyCharacterMapFile.string(), R_OK)) { + keyMapInfo.keyCharacterMapFile.clear(); + } else { + haveKeyCharacterMap = true; + } + } + + if (haveKeyLayout || haveKeyCharacterMap) { + selectKeyMap(keyMapInfo, keyMapName, defaultKeyMap); + } + return haveKeyLayout && haveKeyCharacterMap; +} + +status_t resolveKeyMap(const String8& deviceName, KeyMapInfo& outKeyMapInfo) { + // As an initial key map name, try using the device name. + String8 keyMapName(deviceName); + char* p = keyMapName.lockBuffer(keyMapName.size()); + while (*p) { + if (*p == ' ') *p = '_'; + p++; + } + keyMapName.unlockBuffer(); + + if (probeKeyMap(outKeyMapInfo, keyMapName, false)) return OK; + + // 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(outKeyMapInfo, keyMapName, true)) return OK; + + // Give up! + keyMapName.setTo("unknown"); + selectKeyMap(outKeyMapInfo, keyMapName, true); + LOGE("Could not determine key map for device '%s'.", deviceName.string()); + return NAME_NOT_FOUND; +} + +void setKeyboardProperties(int32_t deviceId, const String8& deviceName, + const KeyMapInfo& keyMapInfo) { + char propName[PROPERTY_KEY_MAX]; + snprintf(propName, sizeof(propName), "hw.keyboards.%u.devname", deviceId); + property_set(propName, deviceName.string()); + snprintf(propName, sizeof(propName), "hw.keyboards.%u.keymap", deviceId); + property_set(propName, keyMapInfo.keyMapName.string()); + snprintf(propName, sizeof(propName), "hw.keyboards.%u.klfile", deviceId); + property_set(propName, keyMapInfo.keyLayoutFile.string()); + snprintf(propName, sizeof(propName), "hw.keyboards.%u.kcmfile", deviceId); + property_set(propName, keyMapInfo.keyCharacterMapFile.string()); +} + +void clearKeyboardProperties(int32_t deviceId) { + char propName[PROPERTY_KEY_MAX]; + snprintf(propName, sizeof(propName), "hw.keyboards.%u.devname", deviceId); + property_set(propName, ""); + snprintf(propName, sizeof(propName), "hw.keyboards.%u.keymap", deviceId); + property_set(propName, ""); + snprintf(propName, sizeof(propName), "hw.keyboards.%u.klfile", deviceId); + property_set(propName, ""); + snprintf(propName, sizeof(propName), "hw.keyboards.%u.kcmfile", deviceId); + property_set(propName, ""); +} + +status_t getKeyCharacterMapFile(int32_t deviceId, String8& outKeyCharacterMapFile) { + char propName[PROPERTY_KEY_MAX]; + char fn[PROPERTY_VALUE_MAX]; + snprintf(propName, sizeof(propName), "hw.keyboards.%u.kcmfile", deviceId); + if (property_get(propName, fn, "") > 0) { + outKeyCharacterMapFile.setTo(fn); + return OK; + } + + const char* root = getenv("ANDROID_ROOT"); + char path[PATH_MAX]; + if (deviceId == DEVICE_ID_VIRTUAL_KEYBOARD) { + snprintf(path, sizeof(path), "%s/usr/keychars/Virtual.kcm", root); + if (!access(path, R_OK)) { + outKeyCharacterMapFile.setTo(path); + return OK; + } + } + + snprintf(path, sizeof(path), "%s/usr/keychars/Generic.kcm", root); + if (!access(path, R_OK)) { + outKeyCharacterMapFile.setTo(path); + return OK; + } + + LOGE("Can't find any key character map files (also tried %s)", path); + return NAME_NOT_FOUND; +} + +static int lookupLabel(const char* literal, const KeycodeLabel *list) { + while (list->literal) { + if (strcmp(literal, list->literal) == 0) { + return list->value; + } + list++; + } + return list->value; +} + +int32_t getKeyCodeByLabel(const char* label) { + return int32_t(lookupLabel(label, KEYCODES)); +} + +uint32_t getKeyFlagByLabel(const char* label) { + return uint32_t(lookupLabel(label, FLAGS)); +} + +static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) { + int32_t newMetaState; + if (down) { + newMetaState = oldMetaState | mask; + } else { + newMetaState = oldMetaState & + ~(mask | AMETA_ALT_ON | AMETA_SHIFT_ON | AMETA_CTRL_ON | AMETA_META_ON); + } + + if (newMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { + newMetaState |= AMETA_ALT_ON; + } + + if (newMetaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { + newMetaState |= AMETA_SHIFT_ON; + } + + if (newMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { + newMetaState |= AMETA_CTRL_ON; + } + + if (newMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { + newMetaState |= AMETA_META_ON; + } + return newMetaState; +} + +static int32_t toggleLockedMetaState(int32_t mask, bool down, int32_t oldMetaState) { + if (down) { + return oldMetaState; + } else { + return oldMetaState ^ mask; + } +} + +int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) { + int32_t mask; + switch (keyCode) { + case AKEYCODE_ALT_LEFT: + return setEphemeralMetaState(AMETA_ALT_LEFT_ON, down, oldMetaState); + case AKEYCODE_ALT_RIGHT: + return setEphemeralMetaState(AMETA_ALT_RIGHT_ON, down, oldMetaState); + case AKEYCODE_SHIFT_LEFT: + return setEphemeralMetaState(AMETA_SHIFT_LEFT_ON, down, oldMetaState); + case AKEYCODE_SHIFT_RIGHT: + return setEphemeralMetaState(AMETA_SHIFT_RIGHT_ON, down, oldMetaState); + case AKEYCODE_SYM: + return setEphemeralMetaState(AMETA_SYM_ON, down, oldMetaState); + case AKEYCODE_FUNCTION: + return setEphemeralMetaState(AMETA_FUNCTION_ON, down, oldMetaState); + case AKEYCODE_CTRL_LEFT: + return setEphemeralMetaState(AMETA_CTRL_LEFT_ON, down, oldMetaState); + case AKEYCODE_CTRL_RIGHT: + return setEphemeralMetaState(AMETA_CTRL_RIGHT_ON, down, oldMetaState); + case AKEYCODE_META_LEFT: + return setEphemeralMetaState(AMETA_META_LEFT_ON, down, oldMetaState); + case AKEYCODE_META_RIGHT: + return setEphemeralMetaState(AMETA_META_RIGHT_ON, down, oldMetaState); + case AKEYCODE_CAPS_LOCK: + return toggleLockedMetaState(AMETA_CAPS_LOCK_ON, down, oldMetaState); + case AKEYCODE_NUM_LOCK: + return toggleLockedMetaState(AMETA_NUM_LOCK_ON, down, oldMetaState); + case AKEYCODE_SCROLL_LOCK: + return toggleLockedMetaState(AMETA_SCROLL_LOCK_ON, down, oldMetaState); + default: + return oldMetaState; + } +} + + +} // namespace android diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp index 8874dfe..f352dbf 100644 --- a/libs/ui/tests/InputDispatcher_test.cpp +++ b/libs/ui/tests/InputDispatcher_test.cpp @@ -67,6 +67,11 @@ private: return false; } + virtual bool dispatchUnhandledKey(const sp<InputChannel>& inputChannel, + const KeyEvent* keyEvent, uint32_t policyFlags) { + return false; + } + virtual void notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, uint32_t policyFlags) { } diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp index 952b974..c6eac25 100644 --- a/libs/ui/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp @@ -118,13 +118,16 @@ void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() { EXPECT_EQ(downTime, keyEvent->getDownTime()); EXPECT_EQ(eventTime, keyEvent->getEventTime()); - status = mConsumer->sendFinishedSignal(); + status = mConsumer->sendFinishedSignal(true); ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK"; - status = mPublisher->receiveFinishedSignal(); + bool handled = false; + status = mPublisher->receiveFinishedSignal(handled); ASSERT_EQ(OK, status) << "publisher receiveFinishedSignal should return OK"; + ASSERT_TRUE(handled) + << "publisher receiveFinishedSignal should have set handled to consumer's reply"; status = mPublisher->reset(); ASSERT_EQ(OK, status) @@ -279,13 +282,16 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent( EXPECT_EQ(samplePointerCoords[offset].orientation, motionEvent->getOrientation(i)); } - status = mConsumer->sendFinishedSignal(); + status = mConsumer->sendFinishedSignal(false); ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK"; - status = mPublisher->receiveFinishedSignal(); + bool handled = true; + status = mPublisher->receiveFinishedSignal(handled); ASSERT_EQ(OK, status) << "publisher receiveFinishedSignal should return OK"; + ASSERT_FALSE(handled) + << "publisher receiveFinishedSignal should have set handled to consumer's reply"; status = mPublisher->reset(); ASSERT_EQ(OK, status) diff --git a/libs/ui/tests/InputReader_test.cpp b/libs/ui/tests/InputReader_test.cpp index de4b05a..ded0225 100644 --- a/libs/ui/tests/InputReader_test.cpp +++ b/libs/ui/tests/InputReader_test.cpp @@ -342,6 +342,12 @@ private: ADD_FAILURE() << "Should never be called by input reader."; } + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) { + ADD_FAILURE() << "Should never be called by input reader."; + return 0; + } + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) { ADD_FAILURE() << "Should never be called by input reader."; return 0; @@ -370,6 +376,7 @@ class FakeEventHub : public EventHubInterface { KeyedVector<int32_t, int32_t> scanCodeStates; KeyedVector<int32_t, int32_t> switchStates; KeyedVector<int32_t, KeyInfo> keys; + KeyedVector<int32_t, bool> leds; Device(const String8& name, uint32_t classes) : name(name), classes(classes) { @@ -444,6 +451,16 @@ public: device->keys.add(scanCode, info); } + void addLed(int32_t deviceId, int32_t led, bool initialState) { + Device* device = getDevice(deviceId); + device->leds.add(led, initialState); + } + + bool getLedState(int32_t deviceId, int32_t led) { + Device* device = getDevice(deviceId); + return device->leds.valueFor(led); + } + Vector<String8>& getExcludedDevices() { return mExcludedDevices; } @@ -577,6 +594,25 @@ private: return result; } + virtual bool hasLed(int32_t deviceId, int32_t led) const { + Device* device = getDevice(deviceId); + return device && device->leds.indexOfKey(led) >= 0; + } + + virtual void setLedState(int32_t deviceId, int32_t led, bool on) { + Device* device = getDevice(deviceId); + if (device) { + ssize_t index = device->leds.indexOfKey(led); + if (index >= 0) { + device->leds.replaceValueAt(led, on); + } else { + ADD_FAILURE() + << "Attempted to set the state of an LED that the EventHub declared " + "was not present. led=" << led; + } + } + } + virtual void dump(String8& dump) { } }; @@ -1690,6 +1726,81 @@ TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) { ASSERT_FALSE(flags[1]); } +TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) { + mFakeEventHub->addLed(DEVICE_ID, LED_CAPSL, true /*initially on*/); + mFakeEventHub->addLed(DEVICE_ID, LED_NUML, false /*initially off*/); + mFakeEventHub->addLed(DEVICE_ID, LED_SCROLLL, false /*initially off*/); + + KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice, -1, + AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC); + addMapperAndConfigure(mapper); + + // Initialization should have turned all of the lights off. + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + + // Toggle caps lock on. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper->getMetaState()); + + // Toggle num lock on. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper->getMetaState()); + + // Toggle caps lock off. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper->getMetaState()); + + // Toggle scroll lock on. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper->getMetaState()); + + // Toggle num lock off. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper->getMetaState()); + + // Toggle scroll lock off. + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0); + process(mapper, ARBITRARY_TIME, DEVICE_ID, + EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML)); + ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL)); + ASSERT_EQ(AMETA_NONE, mapper->getMetaState()); +} + // --- TrackballInputMapperTest --- |