This post i found in Internet
While figuring out hardware buttons for my NITDroid project, I had the opportunity of exploring the way Linux and Android handle input events internally before passing them through to the user application. This post traces the propagation of an input event from the Linux kernel through the Android user space as far as I understand it. Although the principles are likely the same for essentially any input device, I will be drawing on my investigations of the drivers for the LM8323 hardware keyboard (
While figuring out hardware buttons for my NITDroid project, I had the opportunity of exploring the way Linux and Android handle input events internally before passing them through to the user application. This post traces the propagation of an input event from the Linux kernel through the Android user space as far as I understand it. Although the principles are likely the same for essentially any input device, I will be drawing on my investigations of the drivers for the LM8323 hardware keyboard (
drivers/input/keyboard/lm8323.c
) and the TSC2005 touchscreen (drivers/input/touchscreen/tsc2005.c
) which are both found inside the Nokia N810.
I. Inside the Linux kernel
Firstly, Linux exposes externally a uniform input event interface for each device as
/dev/input/eventX
where X
is an integer. This means these "devices" can be polled in the same way
and the events they produce are in the same uniform format. To
accomplish this, Linux has a standard set of routines that every device
driver uses to register / unregister the hardware it manages and publish
input events it receives.
When the driver module of an input device is
first loaded into the kernel, its initialization routine usually sets up
some sort of probing to detect the presence of the types of hardware it
is supposed to manage. This probing is of course device-specific;
however, if it is successful, the module will eventually invoke the
function
input_register_device(…)
in include/linux/input.h
which sets up a file representing the physical device as /dev/input/eventX
where X
is some integer. The module will also register a function to handle IRQs originating from the hardware it manages via request_irq(…)
(include/linux/interrupt.h
) so that the module will be notified when the user interacts with the physical device it manages.
When the user physically interacts with the
hardware (for instance by pushing / releasing a key or exerting /
lifting pressure on the touchscreen), an IRQ is fired and Linux invokes
the IRQ handler registered by the corresponding device driver. However,
IRQ handlers by custom must return quickly as they essentially block the
entire system when executing and thus cannot perform any lengthy
processing; typically, therefore, an IRQ handler would merely 1) save
the data carried by the IRQ, 2) ask the kernel to schedule a method that
would process the event later on when we have exited IRQ mode, and 3)
tell the kernel we have handled the IRQ and exit. This could be very
straightforward, as the IRQ handler in the driver for the LM8323
keyboard inside the N810:
/*
* We cannot use I2C in interrupt context, so we just schedule work.
*/
static irqreturn_t lm8323_irq(int irq, void *data)
{
struct lm8323_chip *lm = data;
schedule_work(&lm->work);
return IRQ_HANDLED;
}
It could also be more complex as the one in the driver of the TSC2005 touchscreen controller (
tsc2005_ts_irq_handler(…)
) as it integrates into the SPI framework (which I have never looked into…).
Some time later, the kernel executes the
scheduled method to process the recently saved event. Invariably, this
method would report the event in a standard format by calling one or
more of the
input_*
functions in include/linux/input.h
; these include input_event(…)
(general purpose), input_report_key(…)
(for key down and key up events), input_report_abs(…)
(for position events e.g. from a touchscreen) among others. Note that the input_report_*(…)
functions are really just convenience functions that call input_event(…)
internally, as defined in include/linux/input.h
.
It is likely that a lot of processing happens before the event is
published via these methods; the LM8323 driver for instance does an
internal key code mapping step and the TSC2005 driver goes through this
crazy arithmetic involving Ohms (to calculate a pressure index from
resistance data?). Furthermore, one physical IRQ could correspond to
multiple published input events, and vice versa. Finally, when all event
publishing is finished, the event processing method calls input_sync(…)
to flush the event out. The event is now ready to be accessed by the userspace at /dev/input/eventX
.
II. Inside the Android userspace
When the Android GUI starts up, an instance of the class
WindowManagerService
(frameworks/base/services/java/com/android/server/WindowManagerservice.java
) is created. This class, when constructed, initializes the member fieldfinal KeyQ mQueue;
where
KeyQ
, defined as a private class inside the same file, extends Android’s basic input handling class, the abstract class KeyInputQueue
(frameworks/base/services/java/com/android/server/KeyInputQueue.java
and frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp
). As mQueue
is instantiated, it of course calls the constructor of KeyInputQueue
; the latter, inconspicuously, starts an anonymous thread it owns that is at the heart of the event handling system in Android:Thread mThread = new Thread("InputDeviceReader") {
public void run() {
...
RawInputEvent ev = new RawInputEvent();
while (true) {
try {
readEvent(ev); // block, doesn't release the monitor
boolean send = false;
...
if (ev.type == RawInputEvent.EV_DEVICE_ADDED) {
...
} else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) {
...
} else {
di = getInputDevice(ev.deviceId);
...
// first crack at it
send = preprocessEvent(di, ev);
}
...
if (!send) {
continue;
}
synchronized (mFirst) {
...
// Is it a key event?
if (type == RawInputEvent.EV_KEY &&
(classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
(scancode < RawInputEvent.BTN_FIRST ||
scancode > RawInputEvent.BTN_LAST)) {
boolean down;
if (ev.value != 0) {
down = true;
di.mKeyDownTime = curTime;
} else {
down = false;
}
int keycode = rotateKeyCodeLocked(ev.keycode);
addLocked(di, curTimeNano, ev.flags,
RawInputEvent.CLASS_KEYBOARD,
newKeyEvent(di, di.mKeyDownTime, curTime, down,
keycode, 0, scancode,
((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0)
? KeyEvent.FLAG_WOKE_HERE : 0));
} else if (ev.type == RawInputEvent.EV_KEY) {
...
} else if (ev.type == RawInputEvent.EV_ABS &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
// Process position events from multitouch protocol.
...
} else if (ev.type == RawInputEvent.EV_ABS &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
// Process position events from single touch protocol.
...
} else if (ev.type == RawInputEvent.EV_REL &&
(classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
// Process movement events from trackball (mouse) protocol.
...
}
...
}
} catch (RuntimeException exc) {
Slog.e(TAG, "InputReaderThread uncaught exception", exc);
}
}
}
};
I have removed most of this ~350 lined
function that is irrelevant to our discussion and reformatted the code
for easier reading. The key idea is that this independent thread will
-
Read an event
-
Call the
preprocess(…)
method of its derived class, offering the latter a chance to prevent the event from being propagated further
3.Add it to the event queue owned by the class
This
InputDeviceReader
thread started by WindowManagerService
(indirectly via KeyInputQueue’s constructor
) is thus THE event loop of the Android UI.
But we are still missing the link from the kernel to this
InputDeviceReader
. What exactly is this magical readEvent(…)
? It turns out that this is actually a native method implemented in the C++ half of KeyInputQueue
:static Mutex gLock;
static sp<EventHub> gHub;
static jboolean
android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
jobject event)
{
gLock.lock();
sp<EventHub> hub = gHub;
if (hub == NULL) {
hub = new EventHub;
gHub = hub;
}
gLock.unlock();
...
bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
&flags, &value, &when);
...
return res;
}
Ah, so
readEvent
is really just a proxy for EventHub::getEvent(…)
. If we proceed to look up EventHub
in frameworks/base/libs/ui/EventHub.cpp
, we findint EventHub::scan_dir(const char *dirname)
{
...
dir = opendir(dirname);
...
while((de = readdir(dir))) {
...
open_device(devname);
}
closedir(dir);
return 0;
}
...
static const char *device_path = "/dev/input";
...
bool EventHub::openPlatformInput(void)
{
...
res = scan_dir(device_path);
...
return true;
}
bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
int32_t* outValue, nsecs_t* outWhen)
{
...
if (!mOpened) {
mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;
mOpened = true;
}
while(1) {
// First, report any devices that had last been added/removed.
if (mClosingDevices != NULL) {
...
*outType = DEVICE_REMOVED;
delete device;
return true;
}
if (mOpeningDevices != NULL) {
...
*outType = DEVICE_ADDED;
return true;
}
...
pollres = poll(mFDs, mFDCount, -1);
...
// mFDs[0] is used for inotify, so process regular events starting at mFDs[1]
for(i = 1; i < mFDCount; i++) {
if(mFDs[i].revents) {
if(mFDs[i].revents & POLLIN) {
res = read(mFDs[i].fd, &iev, sizeof(iev));
if (res == sizeof(iev)) {
...
*outType = iev.type;
*outScancode = iev.code;
if (iev.type == EV_KEY) {
err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
...
} else {
*outKeycode = iev.code;
}
...
return true;
} else {
// Error handling
...
continue;
}
}
}
}
...
}
}
Again, most of the details have been stripped out from the above code, but we now see how
readEvent()
in KeyInputQueue
is getting these events from Linux: on first call, EventHub::getEvent
scans the directory /dev/input
for input devices, opens them and saves their file descriptors in an array called mFDs
. Then whenever it is called again, it tries to read from each of these input devices by simply calling the read(2)
Linux system call.
OK, now we know how an event propagates through
EventHub::getEvent(…)
to KeyInputQueue::readEvent(…)
then to InputDeviceReader.run(…)
where it could get queued inside WindowManagerService.mQueue
(which, as a reminder, extends the otherwise abstract KeyInputQueue
). But what happens then? How does that event get to the client application?
Well, it turns out that
WindowManagerService
has yet another private member class that handles just that:private final class InputDispatcherThread extends Thread {
...
@Override public void run() {
while (true) {
try {
process();
} catch (Exception e) {
Slog.e(TAG, "Exception in input dispatcher", e);
}
}
}
private void process() {
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
...
while (true) {
...
// Retrieve next event, waiting only as long as the next
// repeat timeout. If the configuration has changed, then
// don't wait at all -- we'll report the change as soon as
// we have processed all events.
QueuedEvent ev = mQueue.getEvent(
(int)((!configChanged && curTime < nextKeyTime)
? (nextKeyTime-curTime) : 0));
...
try {
if (ev != null) {
curTime = SystemClock.uptimeMillis();
int eventType;
if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {
eventType = eventType((MotionEvent)ev.event);
} else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
ev.classType == RawInputEvent.CLASS_TRACKBALL) {
eventType = LocalPowerManager.BUTTON_EVENT;
} else {
eventType = LocalPowerManager.OTHER_EVENT;
}
...
switch (ev.classType) {
case RawInputEvent.CLASS_KEYBOARD:
KeyEvent ke = (KeyEvent)ev.event;
if (ke.isDown()) {
lastKey = ke;
downTime = curTime;
keyRepeatCount = 0;
lastKeyTime = curTime;
nextKeyTime = lastKeyTime
+ ViewConfiguration.getLongPressTimeout();
} else {
lastKey = null;
downTime = 0;
// Arbitrary long timeout.
lastKeyTime = curTime;
nextKeyTime = curTime + LONG_WAIT;
}
dispatchKey((KeyEvent)ev.event, 0, 0);
mQueue.recycleEvent(ev);
break;
case RawInputEvent.CLASS_TOUCHSCREEN:
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_TRACKBALL:
dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
configChanged = true;
break;
default:
mQueue.recycleEvent(ev);
break;
}
} else if (configChanged) {
...
} else if (lastKey != null) {
...
} else {
...
}
} catch (Exception e) {
Slog.e(TAG,
"Input thread received uncaught exception: " + e, e);
}
}
}
}
As we can see, this thread started by
WindowManagerService
is very simple; all it does is-
Grabs events queued into
WindowManagerService.mQueue
-
Calls
WindowManagerService.dispatchKey(…)
when appropriate.
If we next inspect
WindowManagerService.dispatchKey(…)
, we would see that it checks the currently focused window, and calls android.view.IWindow.dispatchKey(…)
on that window. The event is now in the user space.
No comments:
Post a Comment