Merge "Fix bug #1776278 (consider supplying our own (unique) eid for calendar entries created on the device)"
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index a5591ba..d09ff41 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -304,6 +304,10 @@
     virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
             const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
 
+    /* Allows the policy a chance to perform default processing for an unhandled key. */
+    virtual bool dispatchUnhandledKey(const sp<InputChannel>& inputChannel,
+            const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
     /* Notifies the policy about switch events.
      */
     virtual void notifySwitch(nsecs_t when,
@@ -609,6 +613,7 @@
         sp<InputChannel> inputChannel;
         sp<InputApplicationHandle> inputApplicationHandle;
         int32_t userActivityEventType;
+        bool handled;
     };
 
     // Generic queue implementation.
@@ -1030,7 +1035,8 @@
             EventEntry* eventEntry, const InputTarget* inputTarget,
             bool resumeWithAppendedMotionSample);
     void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
-    void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+    void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+            bool handled);
     void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
     void drainOutboundQueueLocked(Connection* connection);
@@ -1061,7 +1067,7 @@
     void onDispatchCycleStartedLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
     void onDispatchCycleFinishedLocked(
-            nsecs_t currentTime, const sp<Connection>& connection);
+            nsecs_t currentTime, const sp<Connection>& connection, bool handled);
     void onDispatchCycleBrokenLocked(
             nsecs_t currentTime, const sp<Connection>& connection);
     void onANRLocked(
@@ -1073,7 +1079,9 @@
     void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
     void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
     void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+    void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry);
     void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+    void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry);
 
     // Statistics gathering.
     void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
index dc9e27a..7efb6cc 100644
--- a/include/ui/InputTransport.h
+++ b/include/ui/InputTransport.h
@@ -250,12 +250,13 @@
     status_t sendDispatchSignal();
 
     /* Receives the finished signal from the consumer in reply to the original dispatch signal.
+     * Returns whether the consumer handled the message.
      *
      * Returns OK on success.
      * Returns WOULD_BLOCK if there is no signal present.
      * Other errors probably indicate that the channel is broken.
      */
-    status_t receiveFinishedSignal();
+    status_t receiveFinishedSignal(bool& outHandled);
 
 private:
     sp<InputChannel> mChannel;
@@ -305,12 +306,12 @@
     status_t consume(InputEventFactoryInterface* factory, InputEvent** outEvent);
 
     /* Sends a finished signal to the publisher to inform it that the current message is
-     * finished processing.
+     * finished processing and specifies whether the message was handled by the consumer.
      *
      * Returns OK on success.
      * Errors probably indicate that the channel is broken.
      */
-    status_t sendFinishedSignal();
+    status_t sendFinishedSignal(bool handled);
 
     /* Receives the dispatched signal from the publisher.
      *
diff --git a/include/ui/PowerManager.h b/include/ui/PowerManager.h
index 5434b4f..dd80318 100644
--- a/include/ui/PowerManager.h
+++ b/include/ui/PowerManager.h
@@ -22,14 +22,10 @@
 
 enum {
     POWER_MANAGER_OTHER_EVENT = 0,
-    POWER_MANAGER_CHEEK_EVENT = 1,
-    POWER_MANAGER_TOUCH_EVENT = 2, // touch events are TOUCH for 300ms, and then either
-                                   // up events or LONG_TOUCH events.
-    POWER_MANAGER_LONG_TOUCH_EVENT = 3,
-    POWER_MANAGER_TOUCH_UP_EVENT = 4,
-    POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events.
+    POWER_MANAGER_BUTTON_EVENT = 1,
+    POWER_MANAGER_TOUCH_EVENT = 2,
 
-    POWER_MANAGER_LAST_EVENT = POWER_MANAGER_BUTTON_EVENT, // Last valid event code.
+    POWER_MANAGER_LAST_EVENT = POWER_MANAGER_TOUCH_EVENT, // Last valid event code.
 };
 
 } // namespace android
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index f9c0b91..7ddb3c7 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
@@ -1416,21 +1413,7 @@
         }
 
         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;
     }
@@ -1770,13 +1753,14 @@
 }
 
 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
@@ -1784,9 +1768,6 @@
         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.
@@ -1798,7 +1779,8 @@
         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,
@@ -1898,7 +1880,8 @@
             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);
@@ -1907,7 +1890,7 @@
             return 0; // remove the callback
         }
 
-        d->finishDispatchCycleLocked(currentTime, connection);
+        d->finishDispatchCycleLocked(currentTime, connection, handled);
         d->runCommandsLockedInterruptible();
         return 1;
     } // release lock
@@ -2945,7 +2928,11 @@
 }
 
 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(
@@ -3014,9 +3001,7 @@
 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();
 
@@ -3031,6 +3016,31 @@
     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();
 
@@ -3039,6 +3049,12 @@
     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.
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 120222c..b91e93a 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -129,11 +129,11 @@
     case AKEYCODE_META_RIGHT:
         return setEphemeralMetaState(AMETA_META_RIGHT_ON, down, oldMetaState);
     case AKEYCODE_CAPS_LOCK:
-        return toggleLockedMetaState(AMETA_CAPS_LOCK_LATCHED, down, oldMetaState);
+        return toggleLockedMetaState(AMETA_CAPS_LOCK_ON, down, oldMetaState);
     case AKEYCODE_NUM_LOCK:
-        return toggleLockedMetaState(AMETA_NUM_LOCK_LATCHED, down, oldMetaState);
+        return toggleLockedMetaState(AMETA_NUM_LOCK_ON, down, oldMetaState);
     case AKEYCODE_SCROLL_LOCK:
-        return toggleLockedMetaState(AMETA_SCROLL_LOCK_LATCHED, down, oldMetaState);
+        return toggleLockedMetaState(AMETA_SCROLL_LOCK_ON, down, oldMetaState);
     default:
         return oldMetaState;
     }
@@ -966,8 +966,8 @@
             // 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);
@@ -1058,11 +1058,11 @@
 
 void KeyboardInputMapper::updateLedStateLocked(bool reset) {
     updateLedStateForModifierLocked(mLocked.capsLockLedState, LED_CAPSL,
-            AMETA_CAPS_LOCK_LATCHED, reset);
+            AMETA_CAPS_LOCK_ON, reset);
     updateLedStateForModifierLocked(mLocked.numLockLedState, LED_NUML,
-            AMETA_NUM_LOCK_LATCHED, reset);
+            AMETA_NUM_LOCK_ON, reset);
     updateLedStateForModifierLocked(mLocked.scrollLockLedState, LED_SCROLLL,
-            AMETA_SCROLL_LOCK_LATCHED, reset);
+            AMETA_SCROLL_LOCK_ON, reset);
 }
 
 void KeyboardInputMapper::updateLedStateForModifierLocked(LockedState::LedState& ledState,
@@ -1228,7 +1228,7 @@
             // 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;
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 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 @@
     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 @@
     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 @@
     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/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 @@
         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 @@
     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 @@
         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 c19147f..ded0225 100644
--- a/libs/ui/tests/InputReader_test.cpp
+++ b/libs/ui/tests/InputReader_test.cpp
@@ -376,6 +376,7 @@
         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) {
@@ -450,6 +451,16 @@
         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;
     }
@@ -584,10 +595,22 @@
     }
 
     virtual bool hasLed(int32_t deviceId, int32_t led) const {
-        return false;
+        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) {
@@ -1703,6 +1726,81 @@
     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 ---