Android触摸屏虚拟按键实现方法

    在前几年流行的手机上,一般都会在底部有触摸按键功能(HOME、BACK等键),而这些触摸按键有些是直接用触摸屏的指定区域来模拟,有些是使用屏下的传感器来检测,而我们本次主要是讨论触摸屏的方式,一般情况下,我们直接在驱动里获取我们要模拟的多个指定区域,判断当前触摸的范围是否在指定区域,是则报以相应的KEYCODE,而最近在阅读https://source.android.com/devices/input/touch-devices这篇文档时, 知道在Android系统里也可能在驱动里面将预定好的区域坐标及键码配置好,由Android Frameworks来获取并进行处理,而我们只需要按要求设计好相应的区域和配置好文件即可,根据该文档,我们需要做如下一些配置步骤(下面以Android7.1源码来说明):

    1.在实现虚拟按键时,内核必须有映射名为virtualkeys.的虚拟按键映射文件,如触摸屏设备驱动程序里的设备名为touchyfeely,则虚拟按键映文件的路径必须为:

      /sys/board_properties/virtualkeys.touchyfeely

      虚拟按键映射文件描述了触摸屏上虚拟按键的坐标和Linux按键代码,该文件是一个纯文本文件,由一系列换行符或冒号分隔的虚拟按键布局描述组成。相应的语法要求如下:

      注释行以“#”开头,只对本行有效。

      每个虚拟按键由6个冒号分隔的数据进行描述:

0x01:版本代码。必须始终为 0x01。

:虚拟按键的 Linux 按键代码。

:虚拟按键中心的 X 轴坐标(以像素为单位)。

:虚拟按键中心的 Y 轴坐标(以像素为单位)。

:虚拟按键的宽度(以像素为单位)。

:虚拟按键的高度(以像素为单位)。

      所有的坐标和尺寸都是根据显示坐标系指定的。

      下面是虚拟按键映射文件的两种格式:

      a.一行

        # All on one line

        0x01:158:55:835:90:55:0x01:139:172:835:125:55:0x01:102:298:835:115:55:0x01:217:412:835:95:55

      b.多行

          # One key per line          0x01:158:55:835:90:55          0x01:139:172:835:125:55          0x01:102:298:835:115:55          0x01:217:412:835:95:55

      这两种格式的示例数据是在一款480*800分辨率的触摸屏上,虚拟按键的centerY坐标为835,比触摸屏的可见区域略低的位置。

    2.按键布局及字符映射

       有了上面的虚拟按键映射文件在内核的实现后,还需要对应的kl和kcm文件,下面是针对上面例子对应的配置文件:

       a.按键布局文件(/system/usr/keylayout/touchyfeely.kl)

            key 158 BACK

            key 139 MENU

            key 102 HOME

            key 217 SEARCH

       b.按键字符映射文件(/system/usr/keychars/touchyfeely.kcm)

type SPECIAL_FUNCTION

    3.为什么是virtualkeys.

       在frameworks/native/services/inputflinger/EventHub.cpp文件中,我们可以看到如下内容:

       status_t EventHub::loadVirtualKeyMapLocked(Device* device) {

           // The virtual key map is supplied by the kernel as a system board property file.

           String8 path;

           path.append("/sys/board_properties/virtualkeys.");

           path.append(device->identifier.name);

           if (access(path.string(), R_OK)) {

              return NAME_NOT_FOUND;

           }

           return VirtualKeyMap::load(path, &device->virtualKeyMap);

       }

       从上面两句path.append可以看出最终的path值就是/sys/board_properties/virtualkeys.了。

    4.文件内容为何是一行或多行以“:”分隔的文本内容?

       上面代码继续往下跟,可以追溯到frameworks/native/libs/input/VirtualKeyMap.cpp文件,代码如下:

        status_t VirtualKeyMap::load(const String8& filename, VirtualKeyMap** outMap) {

            *outMap = NULL;

            Tokenizer* tokenizer;

            status_t status = Tokenizer::open(filename, &tokenizer);

            if (status) {

                ALOGE("Error %d opening virtual key map file %s.", status, filename.string());

            } else {

                VirtualKeyMap* map = new VirtualKeyMap();

                if (!map) {

                    ALOGE("Error allocating virtual key map.");

                    status = NO_MEMORY;

                } else {

        #if DEBUG_PARSER_PERFORMANCE

                    nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);

        #endif

                    Parser parser(map, tokenizer);

                    status = parser.parse();

        #if DEBUG_PARSER_PERFORMANCE

                    nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;

                    ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",

                            tokenizer->getFilename().string(), tokenizer->getLineNumber(),

                            elapsedTime / 1000000.0);

        #endif

                    if (status) {

                        delete map;

                    } else {

                        *outMap = map;

                    }

                }

                delete tokenizer;

            }

            return status;

        }

       从上面代码可以看到重点语句status = parser.parse();,即会调用到同一文件的parse函数,其代码如下:

        status_t VirtualKeyMap::Parser::parse() {

            while (!mTokenizer->isEof()) {

        #if DEBUG_PARSER

                ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),

                        mTokenizer->peekRemainderOfLine().string());

        #endif

                mTokenizer->skipDelimiters(WHITESPACE);

                if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {

                    // Multiple keys can appear on one line or they can be broken up across multiple lines.

                    do {

                        String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);

                        if (token != "0x01") {

                            ALOGE("%s: Unknown virtual key type, expected 0x01.",

                                  mTokenizer->getLocation().string());

                            return BAD_VALUE;

                        }

                        VirtualKeyDefinition defn;

                        bool success = parseNextIntField(&defn.scanCode)

                                && parseNextIntField(&defn.centerX)

                                && parseNextIntField(&defn.centerY)

                                && parseNextIntField(&defn.width)

                                && parseNextIntField(&defn.height);

                        if (!success) {

                            ALOGE("%s: Expected 5 colon-delimited integers in virtual key definition.",

                                  mTokenizer->getLocation().string());

                            return BAD_VALUE;

                        }

        #if DEBUG_PARSER

                        ALOGD("Parsed virtual key: scanCode=%d, centerX=%d, centerY=%d, "

                                "width=%d, height=%d",

                                defn.scanCode, defn.centerX, defn.centerY, defn.width, defn.height);

        #endif

                        mMap->mVirtualKeys.push(defn);

                    } while (consumeFieldDelimiterAndSkipWhitespace());

                    if (!mTokenizer->isEol()) {

                        ALOGE("%s: Expected end of line, got '%s'.",

                                mTokenizer->getLocation().string(),

                                mTokenizer->peekRemainderOfLine().string());

                        return BAD_VALUE;

                    }

                }

                mTokenizer->nextLine();

            }

            return NO_ERROR;

        }

        从上面可以看到为何以"0x01"开头了吧,接下来再调用parseNextIntField函数来解析剩下的5个数据,该函数代码如下:

        bool VirtualKeyMap::Parser::parseNextIntField(int32_t* outValue) {

            if (!consumeFieldDelimiterAndSkipWhitespace()) {

                return false;

            }

            String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);

            char* end;

            *outValue = strtol(token.string(), &end, 0);

            if (token.isEmpty() || *end != '\0') {

                ALOGE("Expected an integer, got '%s'.", token.string());

                return false;

            }

            return true;

        }

        从上面可以看出这些数据都会转为整型,其中分隔符WHITESPACE_OR_FIELD_DELIMITER定义如下:

        static const char* WHITESPACE_OR_FIELD_DELIMITER = " \t\r:";

    5.上面分析了驱动的虚拟按键映射文件对应的解析需求,那么我们实际的驱动文件要如何在原有的触摸屏驱动基础上添加相应的文件结点生成处理呢?(当然也可以独立为一个驱动来实现)

        下面是抽象出来的映射文件生成及数据处理需要的代码:

        #define TOUCHYFEELY_KEY_HOME    102

        #define TOUCHYFEELY_KEY_MENU    139

        #define TOUCHYFEELY_KEY_BACK      158

        #define TOUCHYFEELY_KEY_SEARCH  217

        static ssize_t virtual_keys_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)

        {

            return sprintf(buf,

            __stringify(EV_KEY) ":" __stringify(TOUCHYFEELY_KEY_HOME) ":298:835:115:55"

             ":" __stringify(EV_KEY) ":" __stringify(TOUCHYFEELY_KEY_MENU) ":172:835:125:55"

             ":" __stringify(EV_KEY) ":" __stringify(TOUCHYFEELY_KEY_BACK) ":55:835:90:55"

             ":" __stringify(EV_KEY) ":" __stringify(TOUCHYFEELY_KEY_SEARCH) ":412:835:95:55"

             "\n");

        }

        static struct kobj_attribute virtual_keys_attr = {

            .attr = {

        .name = "virtualkeys.touchyfeely", 

                .mode = S_IRUGO,

            },

            .show = &virtual_keys_show,

        };

        static struct attribute *properties_attrs[] = {

                &virtual_keys_attr.attr,

                NULL

        };

        static struct attribute_group properties_attr_group = {

            .attrs = properties_attrs,

        };

        static void touchyfeely_virtual_keys_init(void)

        {

            int ret;

            struct kobject *properties_kobj;


        properties_kobj = kobject_create_and_add("board_properties", NULL);

            if (properties_kobj)

                ret = sysfs_create_group(properties_kobj,  &properties_attr_group);

            if (!properties_kobj || ret)

                pr_err("failed to create board_properties\n");    

        }

        接下来,我们需要在probe函数调用上面的init函数,代码如下:

        static int touchyfeely_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)

        {

            /*...*/

            touchyfeely_virtual_keys_init();

       /*...*/

            set_bit(KEY_HOME,  input_dev->keybit);

            set_bit(KEY_MENU,  input_dev->keybit);

            set_bit(KEY_BACK,  input_dev->keybit);

            set_bit(KEY_SEARCH,  input_dev->keybit);

            /*...*/

        }

        至此,我们对虚拟按键需要实现的内容和配置文件有了清晰的了解。

    6.参考网址

        http://www.phonesdevelopers.info/1690353/

        http://www.cnblogs.com/aceheart/archive/2012/10/27/2742309.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容