Android 虚拟按键上报

Posted bobuddy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 虚拟按键上报相关的知识,希望对你有一定的参考价值。

概述
 本文主要讲述触摸屏上可能用到的虚拟按键menu、home、return,底层驱动的实现和相关实现原理,其中和上层有联系的只是概述。

两种实现方式
 对于触摸按键的发送可以分为两种方法:

1. android提供的 virtualkey's 架构方法,

2. 直接报告key event的方法。

报告keyevent方法

在驱动中添加所支持的按键类型,报告支持事件类型

__set_bit(EV_SYN, input_dev->evbit);  //同步事件

__set_bit(EV_KEY, input_dev->evbit);  //按键事件

报告支持的按键

__set_bit(KEY_HOME, input_dev->keybit);  // home按键

__set_bit(KEY_BACK, input_dev->keybit);  // back按键

__set_bit(KEY_MENU, input_dev->keybit);  // menu按键

触摸屏上的三个按键对应的坐标

(KEY_BACK) 120:1400

(KEY_HOME) 360:1400

(KEY_MENU) 500:1400

keyevent的报告方法很简单只要报告相应的 key 和设备同步sync就可以了

static void ft5x0x_report_value(struct ft5x0x_ts_data *data)

    struct ts_event *event = &data->event;
    int i;
    for (i = 0; i < event->touch_point; i++)
    
        if (event->au16_y[i]==1400)
        
            if(event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2)
            
                switch(event->au16_x[i])
                
                case 120:
                    input_report_key(data->input_dev, KEY_BACK, 1);
                    break;
                case 360: 
                    input_report_key(data->input_dev, KEY_HOME, 1);
                    break;
                case 500: 
                    input_report_key(data->input_dev, KEY_MENU, 1);
                    break;    
                default: break;
                
            
            else
            
                switch(event->au16_x[i])
                
                case 120:
                    input_report_key(data->input_dev, KEY_BACK, 0);
                    break;
                case 360: 
                    input_report_key(data->input_dev, KEY_HOME, 0);
                    break;
                case 500: 
                    input_report_key(data->input_dev, KEY_MENU, 0);
                    break;    
                default: break;
                
            
            input_sync(data->input_dev);
            return;
        

       首先看最外围的for循环,可以看出:上报次数就是触摸屏检测到的点数,当然不会超出触摸屏最大支持的点数。并且每次上报完毕,都会发送一个同步事件 input_sync(),来表示此时上报结束。

       里面的if (event->au16_y[i]==1400) 是在判断触摸点的y轴坐标,一般来说虚拟按键的位置都在一排上,因此必定会有一个轴的坐标是相同的,这里明显三个虚拟按键的y轴坐标相同。

       这个判断if(event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2) 根据前后的代码逻辑,应该是判断按键是按下还是抬起。如果是按下,再进入switch分支,判断另一个x轴的坐标,去上报不同的按键KEY_BACK、KEY_HOME、KEY_MENU。这是按下按键时的上报,input_report_key(data->input_dev, KEY_BACK, 1);.如果是按键抬起,就会上报input_report_key(data->input_dev, KEY_BACK, 0),此函数中只有最后一个参数1、0表明是按键的按下还是抬起事件。但不论是按下还是抬起都会发送 input_sync() 同步事件表明此次上报结束。

Virtualkeys 方法
     virtualkeys是android提供的架构使用起来简单方便,推荐大家使用。以经常使用的ft5x06的触摸屏为例:

#ifdef CONFIG_XXX_RMI4_VIRTUAL_KEY_SUPPORT
struct kobject *d10a_rmi4_virtual_key_properties_kobj;
 
static ssize_t d10a_rmi4_virtual_keys_register(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
    
       return snprintf(buf, 200,
           __stringify(EV_KEY) ":" __stringify(KEY_MENU)  ":101:1343:120:96" \\
         ":" __stringify(EV_KEY) ":" __stringify(KEY_HOMEPAGE)  ":360:1343:150:96" \\
         ":" __stringify(EV_KEY) ":" __stringify(KEY_BACK)  ":618:1343:120:96" "\\n");

 
static struct kobj_attribute d10a_rmi4_virtual_keys_attr =
       .attr =
               .name = "virtualkeys.synaptics_rmi4_i2c",
               .mode = S_IRUGO,
       ,
       .show = &d10a_rmi4_virtual_keys_register,
;
 
#ifdef D10A_SUPPORT_KEY_VIRTUAL_EXCHANGE
static ssize_t synaptics_virtual_exchange_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
    
   return sprintf(buf, "%d\\n", synaptics_key_exchange_enable);

 
static ssize_t synaptics_virtual_exchange_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n)

    int virtual_exchange;
    
    if (sscanf(buf, "%d", &virtual_exchange) == 1)
        pr_err(KERN_ERR "%s: set virtual_exchange= %d \\n", __func__, virtual_exchange);        
        synaptics_key_exchange_enable = !!virtual_exchange;
        
        pr_err(KERN_ERR "%s: set synaptics_key_exchange_enable= %d \\n", __func__,        synaptics_key_exchange_enable);        
 
        return n;
    
    
    return -EINVAL;

 
static struct kobj_attribute d10a_rmi4_key_virtual_exchange_attr =
    .attr =
        .name = "virtual_exchange",
        .mode =     S_IRUGO,
    ,
    .show = &synaptics_virtual_exchange_show,
    .store = &synaptics_virtual_exchange_store,    
;
#endif
 
static struct attribute *d10a_rmi4_virtual_key_properties_attrs[] =
       &d10a_rmi4_virtual_keys_attr.attr,
#ifdef D10A_SUPPORT_KEY_VIRTUAL_EXCHANGE
    &d10a_rmi4_key_virtual_exchange_attr.attr,
#endif
       NULL,
;
 
static struct attribute_group d10a_rmi4_virtual_key_properties_attr_group =
       .attrs = d10a_rmi4_virtual_key_properties_attrs,
;
#endif
    if (d10a_rmi_virtual_key_support)
        printk("linxc:  synaptics virtual_key_support =%d\\n", 
                d10a_rmi_virtual_key_support);
 
        d10a_rmi4_virtual_key_properties_kobj = 
                kobject_create_and_add("board_properties", NULL);
 
        if (d10a_rmi4_virtual_key_properties_kobj) 
            retval = sysfs_create_group(d10a_rmi4_virtual_key_properties_kobj, 
                   &d10a_rmi4_virtual_key_properties_attr_group);
 
        if( !d10a_rmi4_virtual_key_properties_kobj ||retval)
              printk("failed to create synaptics d10a board_properties\\n");
    
virtualkeys方法的实现原理
virtualkey 是基于sysfs文件系统实现的。上层要想访问到virtualkey,必须在sys下面实现这样的文件结点:

/sys/board_properties/virtualkeys.deviceName

virtualkey的实现方式为什么必须基于sysfs文件结点,这个是由上层的调用决定的

在frameworks层,InputManger.java这个函数里定义了上层是怎么访问virtualkey的。

public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName)
            ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>();
            try
                FileInputStream fis = new FileInputStream(
                        "/sys/board_properties/virtualkeys." + deviceName);
    ...
               
    ...

所以,要实现sys文件结点有2个函数

int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr);

int sysfs_create_group(struct kobject *kobj,const struct attribute_group *grp);

这里我们使用的是

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

       因为这个函数在kobject对应的目录里,还可以创建子目录:virtualkeys.deviceName,Linux内核里是用attribute_group来实现子目录.

如图所示,存在sys/board_properties/virtualkey_synaptics_rmi4_i2c 这样的文件节点

      一个kobject对应sysfs里的一个目录,而目录下的文件就是由sysfs_ops和attribute来实现的,其中,attribute定义了kobject的属性,在sysfs里对应一个文件,sysfs_ops用来定义读写这个文件的方法。  

static struct kobj_attribute d10a_rmi4_key_virtual_exchange_attr =
    .attr =
        .name = "virtual_exchange",
        .mode =     S_IRUGO,
    ,
    .show = &synaptics_virtual_exchange_show,
    .store = &synaptics_virtual_exchange_store,    
;
基于此理解,所以board_properties目录,就定义为kobject:

struct kobject *properties_kobj;
    
properties_kobj = kobject_create_and_add("board_properties", NULL);
    就是这样就实现了sys/board_properties目录.

   因为目录下的文件是由sysfs_ops和attribute来实现的,所以目录下的文件virtualkeys.deviceName将定义成attribute,那么对attribute的fops怎么定义呢?

     fops是定义了对以“virtualkeys.deviceName”为name的attribute的操作函数。 在内核的定义中,attribute 的fops就是对应每个attribute自己的show/store函数.

内核定义了一个kobj_attribute 的结构体。

struct kobj_attribute
    struct attribute attr;
    ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
            char *buf);
    ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
             const char *buf, size_t count);
;
        这样,每一个attribute就会有各自属于自己的show/store函数,这样就极大的提高了灵活性。可是,sysfs是通过kobject里的kobj_type->sysfs_ops来读写attribute的,那如果要利用kobj_attribute中的show/store来读写attribute的话,就必须在kobj_type->sysfs_ops里指定。

       kobj_attribute是内核提供给我们的一种更加灵活的处理attribute的方式。只有当我们使用kobject_create来创建kobject时,使用kobj_attribute才比较方便。

    每一个虚拟按键有六个参数:

0x01: A version code. Must always be 0x01.
<Linux key code>: The Linux key code of the virtual key.
<centerX>: The X pixel coordinate of the center of the virtual key.
<centerY>: The Y pixel coordinate of the center of the virtual key.
<width>: The width of the virtual key in pixels.
<height>: The height of the virtual key in pixels.
#define FT5X0X_KEY_HOME    102
#define FT5X0X_KEY_MENU    139
#define FT5X0X_KEY_BACK    158
#define FT5X0X_KEY_SEARCH  217 //定义key code
    __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_HOME) ":0:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_MENU) ":160:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_BACK) ":300:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_SEARCH) ":450:849:120:40" //定义X  Y,width,heigh
      下面还要配置 /system/usr/keylayout .kl文件和.kcm文件。如果没有指定,Android会自动android系统会自动选择默认的\\android\\sdk\\emulator\\keymaps\\目录下的qwerty.kl和qwerty.kcm文件。

         这个就是利用了 android 系统默认的 qwerty.kl 映射表。

        如果需要自己建立“驱动名称.kl”文件,则可以按照 qwerty.kl文件的样式自己建立,把“驱动名称.kl”文件放放置在 android 文件系统中/system/usr/keylayout 目录下即可。

        如果想要android 源码编译过程中将“驱动名称.kl”编译到文件系统中,则只需将文件放置到 android 源码/vender/sec/sec_proprietary/utc100/keychars 目录中,并修改该目录下的 Android.mk 文件。

        Android 系统对底层按键的处理方法
        Android 按键的处理是由 Window Manager 负责,主要的键值映射转换实现是在 android 源代码 frameworks/base/libs/ui/EventHub.cpp 此文件处理来自底层的所有输入事件,并根据来源对事件进行分类处理,

        对于按键事件处理过程如下:

a) 记录驱动名称

b) 获取环境变ANDROID_ROOT 为系统路径(默认是/system,在 android 源代码 system/core/rootdir/init.rc 文件中)

c) 查找路径为“system/usr/keylayout/ft5x0x_ts.kl”的按键映射文件,如果该文件(“ft5x0x_ts.kl”)不存在,则默认用路径为“系统路径/usr/keylayout/qwerty.kl”文件中的键值映射表。

        这样关于virtualkey的底层驱动就实现了。同时基于此驱动,按4个virtualkey时,如果硬件上实现了马达,并且上层有实现按键振动功能,就会有振动产生。

触摸屏属性配置相关文件
        idc文件,用来配置触摸屏的一些属性。下面所提到的system、和data目录都在此路径下

路径:out\\target\\product\\XX\\

文件所在目录访问顺序:

        首先ANDROID_ROOT/usr/idc目录下去找相应名字的文件并返回完整的路径名,如果找不到就从ANDROID_DATA/system/devices/idc下面去找,这里ANDROID_ROOT一般指的是/system目录,ANDROID_DATA一般指/data目录。

        文件名称的查找顺序首先是Vendor_XXXX_Product_XXXX_Version_XXXX.idc,然后是Vendor_XXXX_Product_XXXX.idc最后是DEVICE_NAME.idc。

        总结来看安卓为输入设备打开配置文件依次会访问。

/system/usr/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
 
/system/usr/idc/Vendor_XXXX_Product_XXXX.idc
 
/system/usr/idc/DEVICE_NAME.idc
 
/data/system/devices/idc/Vendor_XXXX_Product_XXXX_Version_XXXX.idc
 
/data/system/devices/idc/Vendor_XXXX_Product_XXXX.idc
 
/data/system/devices/idc/DEVICE_NAME.idc
文本内容:

touch.deviceType = touchScreen
touch.orientationAware = 1
 
keyboard.layout = qwerty
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1
 
cursor.mode = navigation
cursor.orientationAware = 1
key layout文件
        key layout文件是android层面的按键映射文件,通过这个文件,用户可以对kernel发送上来的按键功能进行重新定义。也就是说,kernel发送上来一个home键,你可以在这里把它映射成一个back键或者其他的。一般情况下不会修改这个文件,因此完全可以使用默认的配置文件。

文本内容:

key 158    BACK

key 139    MENU

key 172    HOME

key 217    SEARCH

这个文件访问顺序:

/system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
/system/usr/keylayout/DEVICE_NAME.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl
/data/system/devices/keylayout/DEVICE_NAME.kl
/system/usr/keylayout/Generic.kl
/data/system/devices/keylayout/Generic.kl
characterMap文件
      characterMap文件是android层面的字符映射文件,比如:你摁下了一个'e'键,平时代表'e',shift+'e'代表'E',casplk+'e'代表'E',alt+'e'可能代表别的意思,这个配置文件就是,做这些映射的。一般情况下这个文件也不用修改。使用默认的就可以。

    文本内容:

key A
    label:                              'A'
    base:                               'a'
    shift, capslock:                    'A'

文件访问路径:

/system/usr/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/system/usr/keychars/Vendor_XXXX_Product_XXXX.kcm
/system/usr/keychars/DEVICE_NAME.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX_Version_XXXX.kcm
/data/system/devices/keychars/Vendor_XXXX_Product_XXXX.kcm
/data/system/devices/keychars/DEVICE_NAME.kcm
/system/usr/keychars/Generic.kcm
/data/system/devices/keychars/Generic.kcm
/system/usr/keychars/Virtual.kcm

以上是关于Android 虚拟按键上报的主要内容,如果未能解决你的问题,请参考以下文章

Android 7.0 Power 按键处理流程

Android 屏蔽下方虚拟按键

android 1080p屏幕 有无虚拟按键的适配

android 4.2.2怎么隐藏虚拟按键

如何将android屏幕下方的虚拟按键隐藏掉

Android适配底部虚拟按键的方法