在高通Fastmmi模式中增强交互方式

在高通Fastmmi模式中增强交互方式

背景

由于之前工厂抱怨 FCT模式不好用。

之前的FCT测试是这样子的:PCBA上夹具,连接USB。

同时,使用上位机程序(ATE)发送指令,人工判断结果以后,发送结果;以及下一条测试指令的情况。

可见,测试一条指令所需的交互次数很多。

现在要求减少AT指令的交互测试,思路有2种:

1、做成自动化的,不再人工发送指令

2、通过现有的功能(例如按键)实现模拟点击确认交互方式的指令

大概是这样子的:

机器 放上 夹具,连接USB

进入 Fastmmi  模式以后 ,自动执行每一项测试项,
- 如果指令能够自动返回,则继续下一项
- 如果指令需要人工判断,则通过定义的按键来 显示 成功或者失败;如果 没有按键,则一直等待,否则就进入下一项
- 循环,直到结束测试。

行动目标

1、即使PC不再发送AT指令,机器自己也能运行自动测试(满足某些情况下)。

2、按下指定的按键,能够直接判断测试项的结果。

行动思路

框架了解

整个MMI的框架如下:

  • MMI Core: Core manages all MMI modules and is responsible for UI control, also responses Diag request from DIAG service.

MMI core is on: /dev/socket/mmi, running as server role. Agent and Diag will connect server
socket

  • MMI Agent: Agent loads each MMI module (mmi_xxx.so) in a single process and communicates with MMI Core via socket.
  • MMI Diag: Diag handles diag command from Host PC and communicates with MMI core via socket.
  • MMI UI: UI is part of MMI core, MMI core responsible for drawing UI components.

源码树如下:

The FASTMMI code is in the folder vendor/qcom/proprietary/fastmmi. Check the source file structure below. 
.
├── Android.mk
├── configure.ac
├── libmmi
...
├── Makefile.am
├── mmi
...
├── mmi.mk
├── module
│   ├── cpu
│   │   ├── Android.mk
│   │   ├── cpu.cpp
│   │   └── Makefile.am
...
│   └── wifi
│       ├── Android.mk
│       ├── Makefile.am
│       ├── wifi.cpp
│       └── wifi_uav.cpp
└── res
...
    ├── layout
    │   ├── ...
    │   ├── layout_wifi.xml
    │   ├── main_wear.xml
    │   └── main.xml
    ├── raw
    │   ├── Android.mk
    │   ├── DroidSansFallback.ttf
    │   ├── LICENSE
    │   ├── NOTICE
    │   └── qualsound.wav
    ├── values
    │   ├── Android.mk
    │   ├── path_config_android.xml
    │   ├── path_config_le.xml
    │   ├── strings.xml
    │   └── strings-zh-rCN.xml
    └── wifi_config
        ├── Android.mk
        └── wpa_supplicant_test.conf
  • libmmi: is the UI controller library, like button, window, text ...
  • mmi :is the main application.
  • module: is the individual mmi test cases.
  • res: is the config and layout resource, and so on.

流程分析

fastmmi的资料比较少,因此,只能基于现有的代码来进行分析推断。

流程很简单,fastmmi初始化,创建了包括ate_test_thread (默认的处理串口数据线程)。at_costomer_thread(新增的FCT专用指令函数)在内的各种线程。

从发送AT指令的线程入手:

路径:vendor/qcom/proprietary/fastmmi/mmi/mmi.cpp

实现了从各路串口中接收数据到buff,根据buff内容的不同而进行处理,我们修改FASTMMI的交互方式的关键就在于:

1、判断现在的layout(图层)是否存在

通过 get_main_module找到主界面(最后需要release_cur_layout释放互斥资源)

通过lay = g_layout_map[mod->config_list[KEY_LAYOUT]];判断当前的lay是否存在(子界面)

2、找到我们需要执行的按钮(例如,passfailed

通过layout.find_button_by_name方法找到对应的按钮

3、通过fastmmi的流程来“模拟”点击。

获取按钮的功能:r->cb = btn->get_cb();

执行按钮的功能:r->cb(r->module);

即:

    /* step one: Get layout */
    module_info *mod = get_main_module();
    if(mod == NULL) {
        ALOGE("%s Not main screen OR Null point",__FUNCTION__);
        break;
    }
    layout *lay = g_layout_map[mod->config_list[KEY_LAYOUT]];
    if(lay == NULL || lay->m_listview == NULL) {
        ALOGE("%s No Main layout",__FUNCTION__);
        break;
    }

    /* step two: Find button */	
    layout *curlay = acquire_cur_layout();
    button *btn = curlay->find_button_by_name("xxx");
	if(btn==NULL) 
    {
        ALOGE("btn xxx not found");
        break;
    }

	/* step three: exec button click */
	runnable_t *r = new runnable_t;

    r->cb = btn->get_cb();
	release_cur_layout(); 
    if((r != NULL) && (r->cb != NULL) && (r->module != NULL)) {
        module_info *rmod = (module_info *)(r->module);
        r->cb(r->module);
    }else {
        ALOGE("btn function not found");
        break;
    }

操作记录

使用什么软件,做了什么操作,改动了什么代码

根据上面的思路,发现FCT自动进入了GPS。不知道怎么回事(恢复了默认版本也一样),后面我使用r54分支进行调试。没有这个问题。

好像是因为我的判断逻辑有错误导致的

实现按键交互

路径:vendor/qcom/proprietary/fastmmi/mmi/input.cpp

确保按键能够向下传递

我们最终的目的是为了能够在key_callback中添加我们需要的特殊处理。首先,需要关心按键事件能否向下传递。

下列的2个函数实现了 按键事件处理以及是否向下传递分发(类似QT的事件发放):

int input_callback(int fd, uint32_t revents, void *data) {
    struct input_event ev;
    int retval;

    retval = ev_get_input(fd, revents, &ev);
    if(retval < 0) {
        MMI_ALOGE("input event get fail\n");
        return -1;
    }

    /**Adjust the value to match LCD resolution*/
    adjust_ev(&ev);

   /**Convert virtual key to KEY code*/
    hook_vkey(&ev, &g_key_map);

    /**Call listener, if return False mean stop here,
  * if return true mean continue process event.
  */
    if(!invoke_listener(ev)) // 如果 invoke_listener 处理了,则不再向下传递
        return 0;

    if(ev.type == EV_KEY) {
        key_callback(ev.type, ev.code, ev.value);
    } else if(ev.type == EV_SW) {
        sw_callback(ev.type, ev.code, ev.value);
    } else if(ev.type == EV_ABS || ev.type == EV_SYN) {
        touch_callback(&ev);
    }

    return 0;
}

static bool invoke_listener(input_event ev) {
    bool ret = true;

    pthread_mutex_lock(&g_listener_mutex);
    if(g_input_listener != NULL)
        ret = g_input_listener->dispatch_event(ev);
    pthread_mutex_unlock(&g_listener_mutex);

    return ret;
}

由于invoke_listener调用了dispatch_event,我们继续看dispatch_event是如何对某些按键进行特殊处理的。

路径:vendor/qcom/proprietary/fastmmi/mmi/input_listener_key.cpp

bool input_listener_key::dispatch_event(input_event ev) {

    layout *lay = this->get_lay();
    char btn_name[64] = { 0 };

    __u16 type = ev.type;
    __u16 code = ev.code;
    __u32 value = ev.value;
    mod_ev_t modev;

    modev.mod = this->get_module();

    int down = ! !value;
    
    if(type == EV_KEY) {
        switch (code) {
        case KEY_BACK: // 在 fastmmi中, KEY_BACK 对应的按键应该是 fail
            strlcpy(btn_name, KEY_FAIL, sizeof(btn_name));
            break;

        case KEY_HOMEPAGE:
            ev.code = KEY_HOME; //change the code to KEY_HOMEPAGE

		// ...
        default:
            break;
        }
    }
    // ...
    
    button *btn = lay->find_button_by_name(btn_name);

    if(btn != NULL) {
        if(down) {
            MMI_ALOGI("button(%s) press down, code=%d", btn->get_name(), code);
            btn->set_color(255, 0, 0, 125);
            cb_t cb = this->get_cb();

            modev.ev = &ev;
            if(cb != NULL)
                cb(&modev);
        } else {
            MMI_ALOGI("button(%s) release, code=%d", btn->get_name(), code);
            btn->set_color(255, 0, 0, 255);
        }
        MMI_ALOGW("Button Pushed");

        invalidate();
    } else {
        MMI_ALOGW("Not find button(%s) in layout(%s)", btn_name, lay->get_layout_path());
    }

#if 0 // 解除对 back 键的屏蔽。
    // 关闭这2行,以使得返回值为真,则最后能够将事件传递下去
    if(code == KEY_BACK)
        return false;
#endif

    return true;
}

处理按键

现在,按下的按键能够调用key_callback了。我们只需要在这里进行我们要的特殊处理即可。

路径:vendor/qcom/proprietary/fastmmi/mmi/input.cpp

省略了针对 有关值的定义以及函数声明。

static int key_callback(int type, int code, int value) {
    int down = ! !value;

    if(type != EV_KEY) {
        return 0;
    }

    if(!down) {
        return 0;
    }
    
    MMI_ALOGI("key:%d release", code);
    
    switch (code)
    {
            
#if 1
    // 用于确认结果
    case KEY_BACK :
        mark_this_test_module_result(module_failed); // 新增的函数
    break; case KEY_ENTER:
        mark_this_test_module_result(module_success);
    break; default:
            return 0;
        break;
    }
#endif

    return 0;
}

按照之前说的“流程分析”,我是这么写的:

很简单,还是按照3步走。

// vendor/qcom/proprietary/fastmmi/mmi/mmi.cpp

// 用于手动标记测试结果成功或者失败
void mark_this_test_module_result(bool result)
{
    int key_test_result = 0;
    
    /* step one: Get layout */
    module_info *mod = get_main_module();
    if(mod == NULL) {
        MMI_ALOGE("%s Not main screen OR Null point",__FUNCTION__);
        return;
    }

    layout *lay = g_layout_map[mod->config_list[KEY_LAYOUT]];;
    if(lay == NULL || lay->m_listview == NULL) {
        MMI_ALOGE("%s No Main layout",__FUNCTION__);
        return;
    }
    
    /* step two: Find button */
    layout *curlay = acquire_cur_layout();
    button *btn_pass = curlay->find_button_by_name("pass");
    button *btn_fail = curlay->find_button_by_name("fail");

    if(btn_pass == NULL && btn_fail == NULL){
        MMI_ALOGE("[%s] FCT Confirm Result Button is NULL", __FUNCTION__);
        release_cur_layout();
        return ;
    }
    
    /* step three: exec button click */
    runnable_t *r = new runnable_t;

    button *btn = NULL;
    r->cb = NULL;
    r->module = NULL;

    if(result == module_success) {
        MMI_ALOGI("[%s] FCT Confirm Result Button is PASS", __FUNCTION__);
        btn = btn_pass;
        btn->set_disabled(true);
        r->cb = btn->get_cb();
    }else  if( result == module_failed) {
        MMI_ALOGI("[%s] FCT Confirm Result Button is FAIL", __FUNCTION__);
        btn = btn_fail;
        btn->set_disabled(true);
        r->cb = btn->get_cb();
    }

    r->module = curlay->module;
    MMI_ALOGE("[%s]:[%s] Receive Terminal Signal:(E)", __FUNCTION__, curlay->module->module);
    release_cur_layout();

    if((r != NULL) && (r->cb != NULL) && (r->module != NULL)) {
        module_info *rmod = (module_info *)(r->module);
        ALOGI("[%s] Callback Activated Module:%s", __FUNCTION__, rmod->module);
        r->cb(r->module);
    }

    if(btn != NULL && btn->get_disabled()){
        ALOGI("[%s] Btn Set Enable", __FUNCTION__);
        btn->set_disabled(false);
        btn = NULL;
    }

    return;
}

这样子就完成了。

新建线程用于自动交互

目的:实现在某个条件下,定期轮询获取需要执行的条目即可。

做法:

1、实现对应的功能,并实现每次调用某个函数则得到不同的结果。

2、按着fastmmi的规范实现模拟按键功能点击(参考如上)

3、在某个时候启动这条线程

// 自动测试 功能 添加
struct test_item_for_keypad_ui {
    char * module_name; // 对应的界面
    char * item_name;  // 测试项名称
    //int need_comfirm; // 如果这一项需要人工确认,则为1,能够自动测试,则为0
};
#define TEST_TIEM_ARRAY_SIZE(obj) (sizeof((obj))/sizeof(struct test_item_for_keypad_ui))

struct test_item_for_keypad_ui  fct_test_item_array[] =
{
    {"KEY",              "Key_Start",               1 }, // 需要处理对应的按键测试内容
    {"LCD",              "Lcd_Start",               1 },
    {"LCM_BACKLIGHT",    "Lcd_Backlight_Start",     1 },
    {"LED",              "Led_Start",               1 },
    {"BUTTON_BACKLIGHT", "Button_Backlight_Start",  1 },
    {"BLUETOOTH",        "BT_Start",                0 },
    {"WIFI",             "WIFI_Start",              0 },
    {"GPS",              "GPS_Start",               0 },
    {"NETSIGNAL",        "Netsignal_Start",         0 },
    {"SIMCARD1",         "Sim_Start",               0 },
    {"SDCARD",           "Sd_Start",                0 },
    {"BATTERY",          "Battery_Start",           0 },
    {"PRIMARY MIC",      "Primary_Mic_Start",       0 },
    {"SPEAKER",          "Speaker_Start" ,          0 },
    {"HANDSET PLAY",     "Headset_Start",           0 },
    {"HEADSET MIC",      "Headset_Mic_Start" ,      0 },
    {"GSENSOR",          "Gsensor_Start",           1 }, // 由于这一项会在内部提前结束,因此必须放在最后(调试这块的功能不再本文关心的范围内)
    // 以下部分 算是 保留项目
    //{"VERSION",          "Version_Start" },
};

static int item_index = 0;

int get_next_test_item(char*buff)
{
    if(!buff) return -1;
    if((item_index ) >= TEST_TIEM_ARRAY_SIZE(fct_test_item_array))
    {
        ALOGE("full\n");
        sleep(5);
        return -1;
    }
    ALOGE("get cmd buff from array : [%s]\n", fct_test_item_array[item_index].item_name);
    ALOGE("item_index / Max : %d/%ld\n", item_index+1, TEST_TIEM_ARRAY_SIZE(fct_test_item_array));
    sprintf(buff, "%s", fct_test_item_array[item_index].item_name);
    item_index ++;

    return 1;
}

void reset_test_item_index(void)
{
    item_index = 0;
}

void get_back_this_test_item(void)
{
    if(item_index)
        item_index--;
}

int is_buff_in_test_array(char *buff)
{
    int i;
    for(i = 0; i < TEST_TIEM_ARRAY_SIZE(fct_test_item_array); i++)
    {
        if(!strcmp(buff, fct_test_item_array[i].item_name))
            return 1;
    }
    return 0;    
}

int is_mod_in_test_array(char *mod_name, char*buff)
{
    int i;
    if(!mod_name || !buff) return -1;
    for(i = 0; i < TEST_TIEM_ARRAY_SIZE(fct_test_item_array); i++)
    {
        // 找到对应的模块
        if(!strcmp(mod_name, fct_test_item_array[i].module_name))
        {
            // 判断此时的命令是否匹配
            if(!strcmp(buff, fct_test_item_array[i].item_name))
            return 1;
        }
            
    }
    return 0;    
}

/*! \enum test_thread_loop_type
 *
 *  判断auto_loop_for_each_cmd_thread进入了哪个if
 */
enum test_thread_loop_type {
    loop_no_set, // 默认状态
    loop_for_end, // 命令结束
    loop_for_build_in_auto_test, // 内置的自动测试命令
    loop_for_page_up_down, // 翻页命令
    loop_for_get_result, // 获取总结果的命令
    loop_for_test_single_item, // 匹配测试的每一项
};

static void *auto_loop_for_each_cmd_thread(void *)
{
    int loop_type = loop_no_set;
    char buff[255];
    void *ate_module = NULL;
    bool ate_test = false;
    button *btn = NULL;
    int ret;

    ALOGE("Schips create auto_loop_for_each_cmd_thread \n");

    signal(SIGUSR1, signal_handler);
	
    while(1)
    {
        sleep(1);
        memset(buff,0,255);
        // wait_for_auto_test(); 实现这个接口的阻塞等待与唤醒即可完成在某个时候自动执行
loop_while:
        while((get_next_test_item(buff)==1))
        {
            ALOGE("this cmd is [%s] \n",buff);

            button *btn = NULL;
            ate_test = false;

            /*step one:Get the main layout */
            module_info *mod = get_main_module();
            if(mod == NULL) {
                ALOGE("%s Not main screen",__FUNCTION__);
                get_back_this_test_item();
                break;
            }
            layout *lay = g_layout_map[mod->config_list[KEY_LAYOUT]];;
            if(lay == NULL || lay->m_listview == NULL) {
                ALOGE("%s No Main layout",__FUNCTION__);
                get_back_this_test_item();
                break;
            } else
            {
                // 检查 当前是否 有 正在运行的模块
                list < item_t * >*items = lay->m_listview->get_items();
                list < item_t * >::iterator iter;
                for(iter = items->begin(); iter != items->end(); iter++)
                {
                    item_t *item = (item_t *) (*iter);
                    module_info *tmod = item->mod;
                    if(tmod->running_state == MODULE_RUNNING)
                    {
                        ALOGI("[%s] FCT module [%s] is in running,please waiting", __FUNCTION__, tmod->module);
                        ate_test = true;
                        get_back_this_test_item();
                        sleep(1);
                        goto loop_while;
                    }
                }
            }

            runnable_t *r = new runnable_t;
            r->cb = NULL;
            r->module = NULL;
			if(is_buff_in_test_array(buff)) // !strcmp(buff,"Key_Start") || !strcmp(buff,"Lcd_Start") || ...
            {
                ALOGI("[%s][%d] Receive ATE Test Command:%s", __FUNCTION__, __LINE__, buff);
                /*step two:check modules running state */ // 由于 这里还需要进行界面切换,因此需要先判断这一步。
                if(lay != NULL && lay->m_listview != NULL)
                {
                    list < item_t * >*items = lay->m_listview->get_items();
                    list < item_t * >::iterator iter;
                    for(iter = items->begin(); iter != items->end(); iter++)
                    {
                        item_t *item = (item_t *) (*iter);
                        module_info *tmod = item->mod;
                        if(tmod->running_state == MODULE_RUNNING){
                            ALOGI("[%s] FCT module %s is in running,please waiting", __FUNCTION__, tmod->module);
                            ate_test = true;
                        }

                        if(!strcmp(tmod->module,"KEY") && !strcmp(buff,"Key_Start"))
                        {
                            clear_all_pushed_keys();
                        }
#if 0
                        if((!strcmp(tmod->module,"KEY") && !strcmp(buff,"Key_Start")) ||
                           (!strcmp(tmod->module,"LCD") && !strcmp(buff,"Lcd_Start")) ||
                           // ...
#else
                           if(is_mod_in_test_array(tmod->module, buff))
#endif
                        {
                            ALOGI("[%s] : [%s]-module, cmd is [%s]", __FUNCTION__, tmod->module, buff);
                            ate_module = tmod;
                            r->module = ate_module;
                            r->cb = lay->m_listview->get_cb();
                            //set_mmi_response(resp_buf_ok);
                            loop_type = loop_for_test_single_item;
                        }
                    }
                }
            }

            if(ate_test)
            {
                ate_module = NULL;
                r->module = NULL;
                r->cb = NULL;
                get_back_this_test_item();
                break;
            }

            // 执行动作
            if(loop_type == loop_no_set)
            {
                ALOGE("[%s][%d] Nothing to do,Start Next Loop", __FUNCTION__, __LINE__);
            }else if((r != NULL) && (r->cb != NULL) && (r->module != NULL))
            {
                module_info *rmod = (module_info *)(r->module);
                ALOGI("[%s] Callback Activated Module:%s Command:%s", __FUNCTION__, rmod->module, buff);
                ALOGE("[%s][%d] cmd is [%s]", __FUNCTION__, __LINE__, buff);

                // 只关心单项测试
                if(loop_type == loop_for_test_single_item)
                {
                    r->cb(r->module);
                }
            }

            if(btn != NULL && btn->get_disabled())
            {
                ALOGI("[%s] Btn Set Enable", __FUNCTION__);
                btn->set_disabled(false);
                btn = NULL;
            }
            sleep(2);

        }
    }

    return NULL;
}

附录:分析r->cb(r->module);

fastmmi是如何实现r->cb(r->module);的,其实我也很好奇,因为没有找到具体的按键功能实现。

所以特意翻了一下代码,看了一下,大概知道是,如果“有添加布局的需求”,那么可以好好研究一下:

1、标记按钮对应的按键功能。

2、实现对应的方法,并绑定功能与对应的组件。(通过C++中STL的map的方式)

其中涉及到 xml 的解析就不说了,纯应用层的东西,很多途径可以实现。

mmi/config.cpp:166: } else if(!xmlStrcmp(attr->name, (const xmlChar *) "onclick")) {

in vendor/qcom/proprietary/fastmmi
===============================
# res/layout/layout_xxx.xml (任意一个)
<layout>
<!--
    ....
-->
    <include layout="footer.xml"/>
</layout>
===============================
# res/layout/footer.xml
  <button
        name="pass"
        onclick="do_pass"
        text="btn_pass"
        h_rel="16"
        w_rel="49"
        x_rel="0"
        y_rel="84"
        color="0x007D7Dff" />
===============================
# mmi/func_map.cpp:
void process_exit(void *m) {
    if(m == NULL) return;

    module_info *mod = (module_info *) m;
    mod->running_state = MODULE_IDLE;
    flush_result();
    module_cleanup(mod);
    ALOGI("[%s]  Test finished with result =%d ", mod->module, mod->result);
    launch_main();
    usleep(100);
    sem_post(&g_sem_mod_complete);
}

void process_exit(void *m, int result) {

    if(m == NULL) {
        MMI_ALOGE("Invalid parameter");
        return;
    }

    module_info *mod = (module_info *) m;

    time(&mod->last_time);
    mod->duration = difftime(mod->last_time, mod->start_time);
    mod->result = result;
    MMI_ALOGI("[%s] Test finished with result=%s, test duration=%f seconds",
              mod->module, MMI_TEST_RESULT(result), mod->duration);
    process_exit(m);
}

static void do_pass(void *m) {
    process_exit(m, SUCCESS);
}

static void do_fail(void *m) {
    sem_post(&g_sem_confirm);
    process_exit(m, FAILED);
}

static func_map_t func_list[] = {
    {"do_cancel", do_cancel},
    {"do_extra_cmd", do_extra_cmd},
    {"do_fail", do_fail},
    {"do_ok", do_ok},
    {"do_report", do_report},
    {"do_page_down", do_page_down},
    {"do_page_up", do_page_up},
    {"do_pass", do_pass},
    {"switch_module", switch_module},
    {"do_reboot", do_reboot},
    {"do_run_all", do_run_all},
    {"do_reset", do_reset},
    {"do_show_fail", do_show_fail},
    {"do_show_all", do_show_all},
#ifdef ANDROID
    {"do_next", do_next},
#endif
    {"do_exit", do_exit},
    {"onchange_poweroff", onchange_poweroff},
    {"onchange_reboot_ffbm", onchange_reboot_ffbm},
    {"onchange_reboot_android", onchange_reboot_android},
};

static unordered_map < string, cb_t > func_map;
void create_func_map() {
    uint32_t i = 0;

    for(i = 0; i < sizeof(func_list) / sizeof(func_map_t); i++) {
        func_map[(string) func_list[i].name] = func_list[i].cb;
    }
}

cb_t get_cb(string func_name) {
    return func_map[func_name];
}
===============================
# libmmi/common.h
typedef void (*cb_t) (void *);
class module_info {
  public:
    char module[64];
    int socket_fd;
    int result;
    pid_t pid;
    int mode;
    int running_state;
    extra_cmd_t extracmd;
    time_t start_time;          //start test time
    double duration;            //test duration
    time_t last_time;           //last time to modify test result data
    char data[SIZE_512];        //module test data

      unordered_map < string, string > config_list;
      module_info(char *mod) {
        if(mod != NULL)
            strlcpy(module, mod, sizeof(module));

        memset(data, 0, sizeof(data));
        result = INT_MAX;
        pid = -1;
        socket_fd = -1;
        extracmd.is_valid = false;
        running_state = MODULE_IDLE;
    }
};

typedef struct {
    char name[32];
    cb_t cb;
} func_map_t;
===============================
# mmi/config.cpp
static void parse_button(xmlNodePtr node, button * btn) {
    xmlAttrPtr attr;
    rect_t rect;

    attr = node->properties;
    while(attr != NULL) {
        char *value = (char *) xmlGetProp(node, (const xmlChar *) attr->name);

        if(value != NULL) {
            if(!xmlStrcmp(attr->name, (const xmlChar *) "name")) {
                btn->set_name(value);
            } else if(!xmlStrcmp(attr->name, (const xmlChar *) "text"))
                btn->set_text(get_string(value));
            else if(!xmlStrcmp(attr->name, (const xmlChar *) "image")) {
                btn->set_image(value);
            } else if(!xmlStrcmp(attr->name, (const xmlChar *) "onclick")) { // 这里对应的是值是 do_pass、do_failed 等
                btn->set_cb(get_cb(value));
            } else if // ...
            } else if(!xmlStrcmp(attr->name, (const xmlChar *) "visibility")) {
                if(!strcmp("invisible", value))
                    btn->set_visibility(false);
                else
                    btn->set_visibility(true);
            }

            xmlFree(value);
        }

        attr = attr->next;
    }
    btn->set_rect(&rect);
}

注:fastmmi 的代码看着还是可圈可点的,供学习的地方也很多。这里提到的事件分发和功能组件化映射只是其中的一小部分。

附录:机器上的按键值

getevent -l
===============================================
add device 5: /dev/input/event3
  name:     "gpio-keys"
  # .
/dev/input/event3: EV_KEY       KEY_F2               DOWN
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
/dev/input/event3: EV_KEY       KEY_F2               UP
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
 #  PTT
/dev/input/event3: EV_KEY       KEY_F1               DOWN
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
/dev/input/event3: EV_KEY       KEY_F1               UP
 #  ..
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
/dev/input/event3: EV_KEY       KEY_F3               DOWN
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
/dev/input/event3: EV_KEY       KEY_F3               UP
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
 # emergency
/dev/input/event3: EV_KEY       KEY_F4               DOWN
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
/dev/input/event3: EV_KEY       KEY_F4               UP
/dev/input/event3: EV_SYN       SYN_REPORT           00000000
===============================================
add device 3: /dev/input/event2
  name:     "qpnp_pon"
  # trick-power
  旋钮 的 按键电源键(POWER, OK)
/dev/input/event2: EV_KEY       KEY_POWER            DOWN
/dev/input/event2: EV_SYN       SYN_REPORT           00000000
/dev/input/event2: EV_KEY       KEY_POWER            UP
/dev/input/event2: EV_SYN       SYN_REPORT           00000000
===============================================
add device 4: /dev/input/event1
  name:     "gpiokey-pulley"
  旋钮 相关 : 音量 上下(OK)
  # anticlockwise
/dev/input/event1: EV_KEY       KEY_VOLUMEUP         DOWN
/dev/input/event1: EV_SYN       SYN_REPORT           00000000
/dev/input/event1: EV_KEY       KEY_VOLUMEUP         UP
/dev/input/event1: EV_SYN       SYN_REPORT           00000000
  # clockwise
/dev/input/event1: EV_KEY       KEY_VOLUMEDOWN       DOWN
/dev/input/event1: EV_SYN       SYN_REPORT           00000000
/dev/input/event1: EV_KEY       KEY_VOLUMEDOWN       UP
/dev/input/event1: EV_SYN       SYN_REPORT           00000000
===============================================
add device 6: /dev/input/event0
  name:     "aw9523-keys"
      
..  ↑  ..
   ← →
拨号键 电源键
1   2   3
4   5   6
7   8   9
*   0   #
      
	# ..
/dev/input/event0: EV_KEY       KEY_ENTER            DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_ENTER            UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
 	#  ↑
/dev/input/event0: EV_KEY       KEY_UP               DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_UP               UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
     # ..
/dev/input/event0: EV_KEY       KEY_BACK             DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_BACK             UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # ←
/dev/input/event0: EV_KEY       KEY_LEFT             DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_LEFT             UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # →
/dev/input/event0: EV_KEY       KEY_RIGHT            DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_RIGHT            UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 拨号键
/dev/input/event0: EV_KEY       KEY_SEND             DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_SEND             UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # ↓
/dev/input/event0: EV_KEY       KEY_DOWN             DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_DOWN             UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 电源键(挂断键)
/dev/input/event0: EV_KEY       KEY_ESC              DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_ESC              UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 1
/dev/input/event0: EV_KEY       KEY_1                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_1                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 2
/dev/input/event0: EV_KEY       KEY_2                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_2                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 3
/dev/input/event0: EV_KEY       KEY_3                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_3                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 4
/dev/input/event0: EV_KEY       KEY_4                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_4                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 5
/dev/input/event0: EV_KEY       KEY_5                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_5                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 6
/dev/input/event0: EV_KEY       KEY_6                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_6                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 7
/dev/input/event0: EV_KEY       KEY_7                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_7                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 8
/dev/input/event0: EV_KEY       KEY_8                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_8                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 9
/dev/input/event0: EV_KEY       KEY_9                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_9                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # *
/dev/input/event0: EV_KEY       KEY_NUMERIC_STAR     DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_NUMERIC_STAR     UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # 0
/dev/input/event0: EV_KEY       KEY_0                DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_0                UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
    # #
/dev/input/event0: EV_KEY       KEY_NUMERIC_POUND    DOWN
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
/dev/input/event0: EV_KEY       KEY_NUMERIC_POUND    UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000
posted @ 2021-03-29 12:35  schips  阅读(1036)  评论(0编辑  收藏  举报