ADROID 2.1 架构解析 7 键盘

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 scancode;

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/KeycodeLabels.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)找到对应的字符值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值