from: http://cyher.net/gnulinux/android/virualkey
1 背景
nexus one工业设计简洁,类似于iphone只有一个按键的设计,只有中间的一个轨迹球。但是android标准键盘是有 HOME,MENU,BACK,SEARCH等,但是同时要保持工业设计。nexus one是这样解决问题的,显示屏是800X480,但是在电容触摸屏是8xx*480的就是比800要大的地方就变成了虚拟按键,模拟了android标准按键。
2 方案
要实现,虚拟按键,在android里面是靠两层协助实现,底层要把虚拟按键在比显示屏多出的地方规定好虚拟按键的位置大小以及键值等,给上层一文件接口。上层java层启动一个服务来读取这一区域的按键响应,这样就是大体的架构。具体实现如下:
2.1 底层虚拟按键功能实现方案
简而言之,就是在内核中把虚拟按键的所有信息给上层给出,用什么方式?就是用sys文件系统的方式,sys文件系统的路径是约定好的所以代码如下实现。给出信息的协议格式是一段连续的字符串,每个按键有六项分别用冒号分割,按键按键之间也是用冒号分割,六项按顺序分别是:
键类型:键值:按键区域中心x坐标:按键区域中心y坐标:按键区域宽:按键区域高
arch/arm/mach-msm/board-mahimahi.c
static ssize_t mahimahi_virtual_keys_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { if (system_rev > 2) { /* center: x: back: 55, menu: 172, home: 298, search 412, y: 835 */ return sprintf(buf, __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":55:835:90:55" ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU) ":172:835:125:55" ":" __stringify(EV_KEY) ":" __stringify(KEY_HOME) ":298:835:115:55" ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":412:835:95:55" "\n"); } else { /* center: x: home: 55, menu: 185, back: 305, search 425, y: 835 */ return sprintf(buf, __stringify(EV_KEY) ":" __stringify(KEY_HOME) ":55:835:70:55" ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU) ":185:835:100:55" ":" __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":305:835:70:55" ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":425:835:70:55" "\n"); } } static struct kobj_attribute mahimahi_virtual_keys_attr = { .attr = { .name = "virtualkeys.synaptics-rmi-touchscreen", .mode = S_IRUGO, }, .show = &mahimahi_virtual_keys_show, }; static struct attribute *mahimahi_properties_attrs[] = { &mahimahi_virtual_keys_attr.attr, NULL }; static struct attribute_group mahimahi_properties_attr_group = { .attrs = mahimahi_properties_attrs, }; struct kobject *properties_kobj; properties_kobj = kobject_create_and_add("board_properties", NULL); if (properties_kobj) ret = sysfs_create_group(properties_kobj, &mahimahi_properties_attr_group); if (!properties_kobj || ret) pr_err("failed to create board_properties\n");
2.2 JAVA上层方案
Java层主要是读取按键信息,然后经过一定的算法,来识别虚拟按键,基本不需要修改,但最好还是熟悉java层的架构这样出问题的时候利于定位
frameworks/base/services/java/com/android/server/KeyInputQueue.java/*这是虚拟按键的类里面包括了VirtualKey所用到的成员变量和按键定位方法*/ static class VirtualKey { int scancode; int centerx; int centery; int width; int height; int hitLeft; int hitTop; int hitRight; int hitBottom; InputDevice lastDevice; int lastKeycode; boolean checkHit(int x, int y) { return (x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom); } void computeHitRect(InputDevice dev, int dw, int dh) { if (dev == lastDevice) { return; } if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "computeHitRect for " + scancode + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY); lastDevice = dev; int minx = dev.absX.minValue; int maxx = dev.absX.maxValue; int halfw = width/2; int left = centerx - halfw; int right = centerx + halfw; hitLeft = minx + ((left*maxx-minx)/dw); hitRight = minx + ((right*maxx-minx)/dw); int miny = dev.absY.minValue; int maxy = dev.absY.maxValue; int halfh = height/2; int top = centery - halfh; int bottom = centery + halfh; hitTop = miny + ((top*maxy-miny)/dh); hitBottom = miny + ((bottom*maxy-miny)/dh); } } /*以下就是与底层接口的函数,如果这个函数和底层接口正常,基本上虚拟按键就能够ok*/ private void readVirtualKeys(String deviceName) { try { FileInputStream fis = new FileInputStream( "/sys/board_properties/virtualkeys." + deviceName); /*这里就是读取kernel给出信息的地方,也就是地层与上层接口的地方,所以整个实现的重点就是这里*/ InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr, 2048); String str = br.readLine(); if (str != null) { String[] it = str.split(":"); if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "***** VIRTUAL KEYS: " + it); final int N = it.length-6; for (int i=0; i<=N; i+=6) { if (!"0x01".equals(it[i])) { Log.w(TAG, "Unknown virtual key type at elem #" + i + ": " + it[i]); continue; } try { VirtualKey sb = new VirtualKey(); sb.scancode = Integer.parseInt(it[i+1]); sb.centerx = Integer.parseInt(it[i+2]); sb.centery = Integer.parseInt(it[i+3]); sb.width = Integer.parseInt(it[i+4]); sb.height = Integer.parseInt(it[i+5]); if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Virtual key " + sb.scancode + ": center=" + sb.centerx + "," + sb.centery + " size=" + sb.width + "x" + sb.height); mVirtualKeys.add(sb); } catch (NumberFormatException e) { Log.w(TAG, "Bad number at region " + i + " in: " + str, e); } } } br.close(); } catch (FileNotFoundException e) { Log.i(TAG, "No virtual keys found"); } catch (IOException e) { Log.w(TAG, "Error reading virtual keys", e); } }
2.3 总结
方案基本上就是这样,主要是调试工作可能需要一段时间,还有如果要做虚拟按键,还需要硬件的支持(超过显示区域的触摸屏区域)。本代码基于android 2.1请根据实际情况修改