一: 用户空间的处理

1.处理的内容和流程

触摸屏和轨迹球上报的是坐标、按下、抬起等信息,信息量比较少。按键处理的过程稍微复杂,从驱动程序到Android的Java层受到的信息,键表示方式经过了两次转化,如图8-4所示。


图8-4Android按键输入的两次转化

键扫描码Scancode是由Linux的Input驱动框架定义的整数类型。键扫描码Scancode经过一次转化后,形成按键的标签KeycodeLabel,是一个字符串的表示形式。按键的标签KeycodeLabel经过转换后,再次形成整数型的按键码keycode。在Android应用程序层,主要使用按键码keycode来区分。


在本地框架层libui的头文件中KeycodeLabels.h,按键码为整数值的格式,其定义KeyCode(枚举值)如下所示:

1. typedefenumKeyCode{

2. kKeyCodeUnknown=0,

3. kKeyCodeSoftLeft=1,

4. kKeyCodeSoftRight=2,

5. kKeyCodeHome=3,

6. kKeyCodeBack=4,

7. //......省略中间按键码

8. }KeyCode;

进而在定义了KeycodeLabels.h中定义了从字符串到整数的映射关系,数组KEYCODES,定义如下所示:

1. staticconstKeycodeLabelKEYCODES[]={//{字符串,整数}

2. {"SOFT_LEFT",1},

3. {"SOFT_RIGHT",2},

4. {"HOME",3},

5. {"BACK",4},

6. {"CALL",5},

7. {"ENDCALL",6},

8. {"0",7},//......数字按键

9. {"1",8},

10. {"2",9},

11. {"3",10},

12. {"4",11},

13. {"5",12},

14. {"6",13},

15. {"7",14},

16. {"8",15},

17. {"9",16},

18. {"STAR",17},

19. //......省略中间按键映射

20. {"MENU",82},

21. //......省略中间按键映射

22. {NULL,0}

23. };

数组KEYCODES表示的映射关系,左列的内容即表示按键标签KeyCodeLabel,右列的内容为按键码KeyCode(与KeyCode的数值对应)。实际上,在按键信息第二次转化的时候就是将字符串类型KeyCodeLabel转化成整数的KeyCode。

KeycodeLabel的Flags的定义如下所示:

1. staticconstKeycodeLabelFLAGS[]={

2. {"WAKE",0x00000001},//可以唤醒睡眠,并通知应用层

3. {"WAKE_DROPPED",0x00000002},//可以唤醒睡眠,不通知应用层

4. {"SHIFT",0x00000004},//自动附加SHIFT

5. {"CAPS_LOCK",0x00000008},//自动附加CAPS_LOCK

6. {"ALT",0x00000010},//自动附加ALT

7. {"ALT_GR",0x00000020},

8. {"MENU",0x00000040},

9. {"LAUNCHER",0x00000080},

10. {NULL,0}

11. };

KeycodeLabel表示按键的附属标识。

提示:frameworks/base/core/Java/android/view/KeyEvent.Java中定义了类android.view.KeyEvent类,其中定义整数类型的数值与KeycodeLabels.h中定义的KeyCode枚举值是对应的。

在本地框架层libui的头文件中KeyCharacterMap.h,定义了按键的字符映射关系,KeyCharacterMap类的定义如下所示:

1. classKeyCharacterMap

2. {

3. public:

4. ~KeyCharacterMap();

5. unsignedshortget(intkeycode,intmeta);

6. unsignedshortgetNumber(intkeycode);

7. unsignedshortgetMatch(intkeycode,constunsignedshort*chars,

8. intcharsize,uint32_tmodifiers);

9. unsignedshortgetDisplayLabel(intkeycode);

10. boolgetKeyData(intkeycode,unsignedshort*displayLabel,

11. unsignedshort*number,unsignedshort*results);

12. inlineunsignedintgetKeyboardType(){returnm_type;}

13. boolgetEvents(uint16_t*chars,size_tlen,

14. Vector<int32_t>*keys,Vector<uint32_t>*modifiers);

15. staticKeyCharacterMap*load(intid);

16. enum{

17. NUMERIC=1,

18. Q14=2,

19. QWERTY=3//orAZERTYorwhatever

20. };

21. }

KeyCharacterMap用于将按键的码映射为文本可识别的字符串(例如,显示的标签等)。KeyCharacterMap是一个辅助的功能:由于按键码只是一个与UI无关整数,通常用程序对其进行捕获处理,然而如果将按键事件转换为用户可见的内容,就需要经过这个层次的转换了。

KeyCharacterMap需要从本地层传送到Java层,JNI的代码路径如下所示:

frameworks/base/core/jni/android_text_KeyCharacterMap.cpp

KeyCharacterMapJava框架层次的代码如下所示:

frameworks/base/core/Java/android/view/KeyCharacterMap.Java

android.view.KeyCharacterMap类是Android平台的API可以在应用程序中使用这个类。

android.text.method中有各种Linstener,可以之间监听KeyCharacterMap相关的信息。DigitsKeyListenerNumberKeyListenerTextKeyListener。

以上关于按键码和按键字符映射的内容是在代码中实现的内容,还需要配合动态的配置文件来使用。在实现Android系统的时候,有可能需要更改这两种文件。

动态的配置文件包括:

  • KL(KeycodeLayout):后缀名为kl的配置文件
  • KCM(KeyCharacterMap):后缀名为kcm的配置文件

Donut及其之前配置文件的路径为:

development/emulator/keymaps/

Eclair及其之后配置文件的路径为:

sdk/emulator/keymaps/

这些配置文件经过系统生成后,将被放置在目标文件系统的/system/usr/keylayout/目录或者/system/usr/keychars/目录中。

提示:kl文件将被直接复职到目标文件系统中;由于尺寸较大,kcm文件放置在目标文件系统中之前,需要经过压缩处理。KeyLayoutMap.cpp负责解析处理kl文件,KeyCharacterMap.cpp负责解析kcm文件。

2.kl:按键布局文件

Android默认提供的按键布局文件主要包括qwerty.kl和AVRCP.kl。qwerty.kl为全键盘的布局文件,是系统中主要按键使用的布局文件;AVRCP.kl用于多媒体的控制,ACRCP的含义为Audio/VideoRemoteControlProfile。

qwerty.kl文件的片断如下所示:

1. key399GRAVE

2. key21

3. key32

4. key43

5. key54

6. key65

7. key76

8. key87

9. key109

10. key110

11. key158BACKWAKE_DROPPED

12. key230SOFT_RIGHTWAKE

13. key60SOFT_RIGHTWAKE

14. key107ENDCALLWAKE_DROPPED

15. key62ENDCALLWAKE_DROPPED

16. key229MENUWAKE_DROPPED

17. #省略部分按键的对应内容

18. key16Q

19. key17W

20. key18E

21. key19R

22. key20T

23. key115VOLUME_UP

24. key114VOLUME_DOWN

在按键布局文件中,第1列为按键的扫描码,是一个整数值;第2列为按键的标签,是一个字符串。即完成了按键信息的第1次转化,将整型的扫描码,转换成字符串类型的按键标签。第3列表示按键的Flag,

SHIFT: 当按下,自动加上SHIFT键值
ALT:当按下,自动加上ALT
CAPS:当按下,自动带上CAPS大写
WAKE:当按下,当设备进入睡眠的时候,按下这个键将唤醒,而且发送消息给应用层。
WAKE_DROPPED:当按下,且设备正处于睡眠,设备被唤醒,但是不发送消息给应用层。

扫描码来自驱动程序,显然不同的扫描码可以对应一个按键标签。表示物理上的两个按键可以对应同一个功能按键。

例如,上面的扫描码为158的时候,对应的标签为BACK,再经过第二次转换,根据KeycodeLabels.h的KEYCODES数组,其对应的按键码为4。

提示:按键布局文件其实同时兼顾了input驱动程序的定义和Android中按键的定义。例如:input驱动程序中定义的数字扫描码KEY_1的数值为2,这里2对应的按键标签也为“1”;input驱动程序中定义字母扫描码KEY_Q的数值为16,这里对应的按键标签也为“Q”。然而移动电话的全键盘毕竟有所不同,因此有一些按键是和input驱动程序的定义没有对应关系的。

kl文件将以原始的文本文件的形式,放置于目标文件系统的/system/usr/keylayout/目录或者/system/usr/keychars/目录中。

3.kcm:按键字符映射文件

kcm表示按键字符的映射关系,主要功能是将整数类型按键码(keycode)转化成可以显示的字符。

qwerty.kcm表示全键盘的字符映射关系,其片断如下所示:

1. [type=QWERTY]

2. #keycodedisplaynumberbasecapsfncaps_fn

3.

4. A'A''2''a''A''#'0x00

5.

6. B'B''2''b''B''<'0x00

7.

8. C'C''2''c''C''9'0x00E7

9.

10. D'D''3''d''D''5'0x00

11.

12. E'E''3''e''E''2'0x0301

13.

14. F'F''3''f''F''6'0x00A5

15.

16. G'G''4''g''G''-''_'

17.

18. H'H''4''h''H''[''{'

19.

20. I'I''4''i''I''$'0x0302

21.

22. J'J''5''j''J'']''}'

23.

24. K'K''5''k''K''"''~'

25.

26. L'L''5''l''L'''''`'

27.

28. M'M''6''m''M''!'0x00

29.

30. N'N''6''n''N''>'0x0303

第一列是转换之前的按键码,第二列之后分别表示转换成为的显示内容(display),数字(number)等内容。这些转化的内容和KeyCharacterMap.h中定义的getDisplayLabel(),getNumber()等函数相对应。

这里的类型,除了QWERTY之外,还可以是Q14(单键多字符对应的键盘),NUMERIC(12键的数字键盘)。

kcm文件将被makekcharmap工具转化成二进制的格式,放在目标系统的/system/usr/keychars/目录中。

二: 移植需要注意的情况

1.EventHub中基本的处理

libui库中frameworks/base/libs/ui中的EventHub.cpp文件是用户输入系统的中枢,主要的功能都是在这个文件中实现的。

EventHub.cpp中定义设备节点所在的路径,内容如下所示:

1. staticconstchar*device_path="/dev/input";//输入设备的目录

在处理过程中,将搜索路径下面的所有Input驱动的设备节点,这在openPlatformInput()中通过调用scan_dir()来实现,scan_dir()将会从目录中查找设备,找到后调用open_device()将其打开。

1. boolEventHub::openPlatformInput(void)

2.

3. {

4.

5. //......省略其他部分的内容

6.

7. res=scan_dir(device_path);

8.

9. returntrue;

10.

11. }

EventHub的getEvent()函数负责处理中完成,处理过程是在一个无限循环之内,调用阻塞的函数等待事件到来。

1. boolEventHub::getEvent(int32_t*outDeviceId,int32_t*outType,

2.

3. int32_t*outScancode,int32_t*outKeycode,uint32_t*outFlags,

4.

5. int32_t*outValue,nsecs_t*outWhen)

6.

7. {

8.

9. while(1){

10.

11. //......省略部分内容

12.

13. pollres=poll(mFDs,mFDCount,-1);//使用poll处理设备节点,进行阻塞

14.

15. //......省略部分内容

16.

17. for(i=1;i<mFDCount;i++){

18.

19. if(mFDs[i].revents){

20.

21. if(mFDs[i].revents&POLLIN){

22.

23. res=read(mFDs[i].fd,&iev,sizeof(iev));//读取信息

24.

25. //......省略部分内容

26.

27. }

28.

29. }

30.

31. }

32.

33. }

poll()函数将会阻塞程序的运行,此时为等待状态,无开销,直到Input设备的相应事件发生,事件发生后poll()将返回,然后通过read()函数读取Input设备发生的事件代码。

注意,EventHub默认情况可以在/dev/input之中扫描各个设备进行处理,通常情况下所有的输入设备均在这个目录中。

实际上,系统中可能有一些input设备可能不需要被Android整个系统使用,也就是说不需要经过EventHub的处理,在这种情况下可以根据EventHub中open_device()函数的处理,设置驱动程序中的一些标志,屏蔽一些设备。open_device()中处理了键盘,轨迹球和触摸屏等几种设备,对其他设备可以略过。另外一个简单的方法就是将不需要EventHub处理的设备的设备节点不放置在/dev/input之中。

open_device()函数还将打开system/usr/keylayout/中的kl文件来处理,处理的过程如下所示:

1. intEventHub::open_device(constchar*deviceName){

2.

3. //......省略部分内容

4.

5. constchar*root=getenv("ANDROID_ROOT");

6.

7. snprintf(keylayoutFilename,sizeof(keylayoutFilename),

8.

9. "%s/usr/keylayout/%s.kl",root,tmpfn);

10.

11. booldefaultKeymap=false;

12.

13. if(access(keylayoutFilename,R_OK)){

14.

15. snprintf(keylayoutFilename,sizeof(keylayoutFilename),

16.

17. "%s/usr/keylayout/%s",root,"qwerty.kl");

18.

19. defaultKeymap=true;

20.

21. }

22.

23. //......省略部分内容

24.

25. }

由此可见,默认情况下使用的就是qwerty.kl,这里只是扫描各个后缀名为kl的文件,然后交由KeyLayoutMap去解析处理,KeyLayoutMap是一个内部使用的类。

2.按键的增加

Android已经定义了比较丰富、完整的标准按键。在一般情况下,不需要为Android系统增加按键,只需要根据kl配置按键即可。在系统中有比较奇特按键的时候,需要更改Android系统的框架层来更改按键。

增加按键需要更改的文件较多,主要的文件如下所示。

  • frameworks/base/include/ui/KeycodeLabels.h:中的KeyCode枚举数值和KeycodeLabel类型Code数组(以NULL为结尾)
  • frameworks/base/core/Java/android/view/KeyEvent.Java:定义整数值,作为平台的API供Java应用程序使用
  • frameworks/base/core/res/res/values/attrs.xml:表示属性的资源文件,需要修改其中的name="keycode"的attr。

示例:

对keypad来说,涉及到的文件有以下几个:
1,/vendor/qcom/7k_ffa_keypad.kl
首先在此文件中,添加新的键值信息:Example: key 123 WLS flag
注意:新加的键值不要与已有的重复。

2.kernel/arch/arm/mach-msm/keypad_surf_ffa.c
在keypad对应的键位添加自定义的键码如 :123

3. frameworks/base/include/ui/keycodeLabels.h
在数组keycodes 中添加 新定义的信息
{ "MUTE", 91 },{ "WLS" , 92 }
枚举类型 KeyCode 中添加
kKeyCodeMute = 91
kKeyCodeWLS= 92

4.frameworks/base/core/res/res/values/attrs.xml
<enum name="KEYCODE_MUTE"value="91"/>
<enum name="KEYCODE_WLS" value="92" />

5. frameworks/base/core/java/android/view/KeyEvent.java
public static final int KEYCODE_MUTE = 91;
public static final int KEYCODE_WLS = 92;
注意: private static final int LAST_KEYCODE = KEYCODE_MUTE;
应改为: private static final int LAST_KEYCODE = KEYCODE_WLS;

函数 public final boolean isSystem() 中,同样需要添加:
case KEYCODE_SEARCH:
case KEYCODE_WLS:

通过以上的更改,新的键值就添加上去了,由于更改了 KeyEvent,影响到了API,
所以需要make update-api

如果对新键值进行处理,可以通过获取相应的keycode,对它进行处理;
对于按键事件的处理一般如下文件中
6. frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java

综上可知,我们可以根据需求定义自己的键值,并对键值所对应的事件信息进行合理化处理。


注:
7k_ffa_keypad.kl是专门给7k_ffa_keypad输入设备使用的keylayout文件,所以修改7k_ffa_keypad.kl文件只是给键盘输入增加键值。如果是其他设备的输入,如x_input,而且当没有x_iput.kl存在的时候,那么默认的kl文件就是qwerty.kl(android-root/development/emulator/keymaps/qwerty.kl)

这个很重要。也就是说需要实现明白添加的键值是否是7k_ffa_keypad的input event

框架层增加完成后,只需要更改kl文件,增加按键的映射关系即可。

提示:在系统需要增加按键的时候,一种简易的做法是使用Android中已经定义的“生僻”按键码作为这个新增按键的键码。使用这种方式Android的框架层不需要做任何改动。这种方式的潜在问题是当某些第三方的应用可能已经使用那些生僻按键时,将意外激发系统的这种新增的按键。



总结:
1.流程是kernel 上报键扫描码Scancode 通过 .kl 文件转化字符串, 再通过KeycodeLabels.h文件中映射 转化为整数型的按键码keycode,  最后传给Android 应用层 
在传给Android 应用层前,有一些拦截的处理可以供我们修改。是在policy/src/com/android/internal/policy/impl/PhoneWindowManager.java 文件的dispatchUnhandledKey->interceptFallback 函数里的 interceptKeyBeforeQueueing和interceptKeyBeforeDispatching两个函数,interceptKeyBeforeQueueing主要处理音量键、电源键、耳机键等,interceptKeyBeforeDispatching主要处理Home键、Menu键、Search键等

2. 对于任意键的唤醒,可以在.kl里添加WAKE_DROPPED,这个参数也是要传给interceptKeyBeforeQueueing和interceptKeyBeforeDispatching函数的policyFlags去进行判断,看看 isWakeKey 这个值,另外 result这个返回值是判断是否要把按键(keyEvent)传给应用程序(ACTION_PASS_TO_USER),和是否要忽略这个唤醒按键(ACTION_WAKE_UP),忽略的操作是由isWakeKeyWhenScreenOff 函数完成的

更多相关文章

  1. android用户输入系统详细说明
  2. Android的NDK开发(1)-不一样的HelloWorld
  3. 跟Google学习Android开发-起始篇-保存数据(1)
  4. cocos2d-x 2.0.1版本的使用 在android 上运行 初学篇(2)
  5. 第四章 常见 Android(安卓)文件格式(一)(库文件、APK)
  6. Android(安卓)Studio如何在项目中使用jni以及OpenCV库
  7. Android(安卓)画板(简单的自定义控件)
  8. android merge标签
  9. Android的内外存储、SharedPreferences(偏好设置)及获取空间使用情

随机推荐

  1. 在Windows系统中使用NDK编译Android二进
  2. 【Android(安卓)基础】Android中全屏或者
  3. Android(安卓)ImageView实现上一页,下一页
  4. Chronometer控件实现的Android计时器
  5. Android(安卓)更新UI的两个方法
  6. Android(安卓)Activity四种加载方式
  7. UI控件--ImageView和ImageButton
  8. Android(安卓)NDK报错(Eclipse)及解决方法
  9. 仿Android疯狂猜图
  10. [Android(安卓)Samples视频系列之ApiDemo