7 键盘
系统在KeyInputQueue服务里创建了一个
线程,在这个线程里不断读取输入事件,然后对这个事件进行处理。
7.1 按键输入
队列服务
按键输入事件有多种方式,如:键盘敲击、
触摸屏触击、鼠标点击及多点触摸,主要是按下和放开的事件。
7.1.1 输入事件的数据结构
文件:frameworks/base/core/java/android/view/RawInputEvent.java
public class RawInputEvent {
// Event class as defined by EventHub.
...
public int deviceId;
public int type;
public int s
cancode;
public int keycode;
public int flags;
public int value;
public long when;
}
7.1.2 输入事件服务
文件:frameworks/base/services/java/com/android/server/KeyInputQueue.java
7.1.2.1 获取事件
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
if (DEBUG) Log.v(TAG, "InputDeviceReader.run()");
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
RawInputEvent ev = new RawInputEvent();
while (true) {
try {
...
// block, doesn't release the monitor
readEvent(ev);
...
调用readEvent,将输入事件读取到ev类,即RawInputEvent的变量里,readEvent对应jni的android_server_KeyInputQueue_readEvent
7.1.2.2 处理键盘事件
...
public static KeyEvent newKeyEvent(InputDevice device, long downTime,
long eventTime, boolean down, int keycode, int repeatCount,
int scancode, int flags) {
return new KeyEvent(
downTime, eventTime,
down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP,
keycode, repeatCount,
device != null ? device.mMetaKeysState : 0,
device != null ? device.id : -1, scancode,
flags | KeyEvent.FLAG_FROM_SYSTEM);
}
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
if (DEBUG) Log.v(TAG, "InputDeviceReader.run()");
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
RawInputEvent ev = new RawInputEvent();
while (true) {
try {
InputDevice di;
// block, doesn't release the monitor
readEvent(ev);
...
final int scancode = ev.scancode;
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));
}
处理键盘事件,并将该事件加入事件队列。
7.2硬件调用
文件:frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp
int register_android_server_KeyInputQueue(JNIEnv* env)
{
...
jclass inputEvent = env->FindClass("android/view/RawInputEvent");
gInputOffsets.mDeviceId
= env->GetFieldID(inputEvent, "deviceId", "I");
gInputOffsets.mType
= env->GetFieldID(inputEvent, "type", "I");
gInputOffsets.mScancode
= env->GetFieldID(inputEvent, "scancode", "I");
gInputOffsets.mKeycode
= env->GetFieldID(inputEvent, "keycode", "I");
gInputOffsets.mFlags
= env->GetFieldID(inputEvent, "flags", "I");
gInputOffsets.mValue
= env->GetFieldID(inputEvent, "value", "I");
gInputOffsets.mWhen
= env->GetFieldID(inputEvent, "when", "J");
...
}
获取android/view/RawInputEvent类的变量。
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();
int32_t deviceId;
int32_t type;
int32_t scancode, keycode;
uint32_t flags;
int32_t value;
nsecs_t when;
bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
&flags, &value, &when);
env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
env->SetIntField(event, gInputOffsets.mType, (jint)type);
env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);
env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);
env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);
env->SetIntField(event, gInputOffsets.mValue, value);
env->SetLongField(event, gInputOffsets.mWhen,
(jlong)(nanoseconds_to_milliseconds(when)));
return res;
}
将从eventhub读到的变量存到android/view/RawInputEvent类对应的变量里。
7.3 EventHub库
文件:frameworks/base/libs/ui/EventHub.cpp
7.3.1 读取输入
设备状态
static const char *device_path = "/dev/input";
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) {
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) {
LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);
if(mFDs[i].revents & POLLIN) {
res = read(mFDs[i].fd, &iev, sizeof(iev));
if (res == sizeof(iev)) {
*outDeviceId = mDevices[i]->id;
if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
*outType = iev.type;
*outScancode = iev.code;
if (iev.type == EV_KEY) {
err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
if (err != 0) {
*outKeycode = 0;
*outFlags = 0;
}
} else {
*outKeycode = iev.code;
}
*outValue = iev.value;
*outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
return true;
} else {
if (res<0) {
LOGW("could not get event (errno=%d)", errno);
} else {
LOGE("could not get event (wrong size: %d)", res);
}
continue;
}
}
}
}
// read_notify() will
modify mFDs and mFDCount, so this must be done after
// processing all other events.
if(mFDs[0].revents & POLLIN) {
read_notify(mFDs[0].fd);
}
}
}
openPlatformInput() 打开/dev/input/ 目录下的所有输入设备文件。
打开设备后,不断轮循所有设备,直到读取有POLLIN事件产生的设备的状态。
7.3.2 导入键盘配置文件
int EventHub::open_device(const char *deviceName)
{
...
if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
//fprintf(stderr, "could not get device name for %s, %s\n", deviceName, strerror(errno));
name[0] = '\0';
}
...
if ((device->classes&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;
}
device->layoutMap->load(keylayoutFilename);
// tell the world about the devname (the descriptive name)
int32_t publicID;
if (!mHaveFirstKeyboard && !defaultKeymap) {
publicID = 0;
// the built-in keyboard
has a well-known device ID of 0,
// this device better not go away.
mHaveFirstKeyboard = true;
mFirstKeyboardId = device->id;
} else {
publicID = device->id;
// ensure mFirstKeyboardId is set to -something-.
if (mFirstKeyboardId == 0) {
mFirstKeyboardId = device->id;
}
}
char propName[100];
sprintf(propName, "hw.keyboards.%u.devname", publicID);
property_set(propName, name);
// 'Q' key support = cheap test of whether this is an alpha-capable kbd
if (hasKeycode(device, kKeyCodeQ)) {
device->classes |= CLASS_ALPHAKEY;
}
// See if this has a DPAD.
if (hasKeycode(device, kKeyCodeDpadUp) &&
hasKeycode(device, kKeyCodeDpadDown) &&
hasKeycode(device, kKeyCodeDpadLeft) &&
hasKeycode(device, kKeyCodeDpadRight) &&
hasKeycode(device, kKeyCodeDpadCenter)) {
device->classes |= CLASS_DPAD;
}
LOGI("New keyboard: publicID=%d device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n",
publicID, device->id, name, propName, keylayoutFilename);
}
...
}
由以上代码可知,会优先加载/
system/usr/keylayout/输入设备名称.kl
如: /sys/class/input/input1/name = keypad,则会加载/system/usr/keylayout/keypad.kl,如果该文件不存在,则加载默认文件/system/usr/keylayout/qwerty.kl,该文件的原型在:sdk/emulator/keymaps/qwerty.kl ,eclair以前的
版本都是放在development/ emulator/keymaps/qwerty.kl
7.3.3 按键映射
如7.3.2所示代码,先加载配置文件:
device->layoutMap->load(keylayoutFilename);
如7.3.1所示代码,再将读取到的按键码进行转换:
err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
文件:frameworks/base/libs/ui/KeyLayoutMap.cpp
status_t
KeyLayoutMap::load(const char* filename)
{
int fd = open(filename, O_RDONLY);
...
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;
}
}
...
}
由以上代码可知,是以如下方式对配置文件进行
解析的:
BEGIN: 如果第一个
关键字是key,则转入SCANCODE,否则退出。
SCANCODE: 将第二个关键字转为数字,即扫描码scancode,转向KEYCODE。
KEYCODE: 将第三个关键字与KEYCODES列表配对,找出关键码keycode,转向FLAG。
FLAG:如果第四个关键字是key,则保存刚扫描的键码,然后转向SCANCODE,否则将该关键字与FLAGS列表配对,找出flags值,然后转向BEGIN。
注:KEYCODES列表和FLAGS列表的定义在:
frameworks/base/include/ui/Keycode
Labels.h
status_t
KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags) const
{
if (m_status != NO_ERROR) {
return m_status;
}
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);
*keycode = k.keycode;
*flags = k.flags;
//LOGD("mapped scancode=%d to keycode=%d flags=0x%08x\n", scancode,
// keycode, flags);
return NO_ERROR;
}
Map的功能是根据scancode,找到对应的keycode.
qwerty.kl 的部分配置:
# scancode keycode flags
key 399 GRAVE
key 2 1
key 3 2
key 4 3
key 5 4
key 6 5
key 7 6
key 8 7
key 9 8
key 10 9
key 11 0
key 158 BACK WAKE_DROPPED
key 230 SOFT_RIGHT WAKE
key 60 SOFT_RIGHT WAKE
7.4 字符映射
7.4.1 加载字符配置表
文件:frameworks/base/core/java/android/view/KeyCharacterMap.java
public static KeyCharacterMap load(int keyboard)
{
...
result = new KeyCharacterMap(keyboard);
...
}
private KeyCharacterMap(int keyboardDevice)
{
mKeyboardDevice = keyboardDevice;
mPointer = ctor_native(keyboardDevice);
}
ctor_native 原型如下:
文件:frameworks/base/core/jni/android_text_KeyCharacterMap.cpp
static JNINativeMethod g_methods[] = {
/* name, signature, funcPtr */
{ "ctor_native", "(I)I", (void*)ctor },
...
};
static jint
ctor(JNIEnv *env, jobject clazz, jint id)
{
return reinterpret_cast<int>(KeyCharacterMap::load(id));
}
KeyCharacterMap::load原型如下:
文件:frameworks/base/libs/ui/KeyCharacterMap.cpp
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;
}
LOGW("Error loading keycharmap file '%s'. %s='%s'", path, propName, dev);
} else {
LOGW("No keyboard for id %d", id);
}
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;
}
LOGW("Using default keymap: %s", path);
return rv;
}
以上所提及代码要实现的功能是:先查看hw.keyboards.%u.devname的属性,若存在则打开/system/usr/keychars/%s.kcm.bin文件,否则打开默认文件:/system/usr/keychars/qwerty.kcm.bin
文件:frameworks/base/libs/ui/EventHub.cpp
参考<<7.3.2 导入键盘配置文件>>里的:
char propName[100];
sprintf(propName, "hw.keyboards.%u.devname", publicID);
property_set(propName, name);
在代码里设置了KeyCharacterMap::load 中hw.keyboards.%u.devname所需要的值,即设备名称,如: /sys/class/input/input1/name = keypad,则会加载/system/usr/keychars/keypad.kcm.bin
例:
/sys/class/input/input1/name = keypad
则要打开的文件路径是:
/system/usr/keylayout/keypad.kl
/system/usr/keychars/keypad.kcm.bin
若以上文件不存在,则使用默认文件:
/system/usr/keylayout/qwerty.kl
/system/usr/keychars/qwerty.kcm.bin
7.4.2字符映射
文件:frameworks/base/core/java/android/view/KeyCharacterMap.java
public char getMatch(int keyCode, char[] chars)
{
return getMatch(keyCode, chars, 0);
}
public char getMatch(int keyCode, char[] chars, int modifiers)
{
if (chars == null) {
// catch it here instead of in native
throw new NullPointerException();
}
return getMatch_native(mPointer, keyCode, chars, modifiers);
}
getMatch_native 原型如下:
文件:frameworks/base/core/jni/android_text_KeyCharacterMap.cpp
static JNINativeMethod g_methods[] = {
/* name, signature, funcPtr */
{ "getMatch_native", "(II[CI)C", (void*)getMatch },
...
};
static jchar
getMatch(JNIEnv *env, jobject clazz, jint ptr, jint keycode, jcharArray chars, jint modifiers)
{
jchar rv;
jchar* ch = env->GetCharArrayElements(chars, NULL);
jsize chsize = env->GetArrayLength(chars);
rv = reinterpret_cast<KeyCharacterMap*>(ptr)->getMatch(keycode, ch, chsize, modifiers);
env->ReleaseCharArrayElements(chars, ch, JNI_ABORT);
return rv;
}
getMatch的原型如下:
文件:frameworks/base/libs/ui/KeyCharacterMap.cpp
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;
}
}
}
}
}
return 0;
}
qwerty.kcm的部分配置:
[type=QWERTY]
# keycode display number base caps fn caps_fn
A 'A' '2' 'a' 'A' '#' 0x00
B 'B' '2' 'b' 'B' '<' 0x00
C 'C' '2' 'c' 'C' '9' 0x00E7
D 'D' '3' 'd' 'D' '5' 0x00
E 'E' '3' 'e' 'E' '2' 0x0301
F 'F' '3' 'f' 'F' '6' 0x00A5
G 'G' '4' 'g' 'G' '-' '_'
H 'H' '4' 'h' 'H' '[' '{'
I 'I' '4' 'i' 'I' '$' 0x0302
J 'J' '5' 'j' 'J' ']' '}'
找到keyCode对应的所有字符,然后再根据modifiers选择对应的字符
7.5 流程总结
当有按键响应时,
内核传给ANDROID的是scancode,ANDROID将scancode经配置表(如qwerty.kl)找到keycode标识符,然后由内部表KEYCODES列表找到keycode的数字值,再由keycode的数字值经字符配置表(如qwerty.kcm)找到对应的字符值。