• Android13 按键kl文件优先级详解


    Android13 按键kl文件优先级详解

    本文专门讲解一下Android 按键接收和处理作用的键值kl文件的选择过程,有需要的可以了解。

    本文具体逻辑和调试是使用Android13代码和系统。

    一、前言

    1、Android 键值相关的"idc"、“kl”、"kcm"基本定义和作用:

    "idc"(Input Device Configuration)为输入设备配置文件,
    它包含设备具体的配置属性,这些属性影响输入设备的行为。对于touch screen设备,总是需要一个idc文件来定义其行为。
    
    "kl"(key layout)文件是一个键值布局映射文件,是标准linux与anroid的键值映射文件,
    
    "kcm"(key code mapping)文件意为按键字符映射文件,作用是将 Android按键代码与修饰符的组合映射到 Unicode字符
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、kl文件选择的大概优先级

    本文kl流程分析主要参考:https://blog.csdn.net/kc58236582/article/details/52199274

    Android kl(key layout)文件是一个映射文件,是标准linux与anroid的键值映射文件,
    kl文件可以有很多个,但是它有一个使用优先级:

    /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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上面这个优先级也是其他文章提供的,并不准确,估计是分析的代码是Android9或者更早的系统代码。

    但是只要你看完后面的分析,自己可以有比较全面认知。

    3、adb查看当前按键使用的kl文件

    (1)主要命令:
    1、通过"getevent"查看事件节点和节点名称;
    2、通过"dumpsys input"查看节点的具体使能的kl文件;
    
    
    • 1
    • 2
    • 3
    (2)命令示例:
    ①getevent
    130|atom:/ # getevent
    //(1)这里可以查看到按键的eventX节点,和节点在内核上的命名名称
    add device 1: /dev/input/event2
      name:     "aw8624_haptic"
    add device 2: /dev/input/event0
      name:     "ACCDET"
    add device 3: /dev/input/event3
      name:     "fts_ts"
    add device 4: /dev/input/event1
      name:     "mtk-kpd"
    
    //(2)按下音量减按键,这里第二列的0001 对应的数据才是有用的数据,可以看到音量减键对应的按键键值是0x72
    /dev/input/event1: 0001 0072 00000001 //(3)1是按下
    /dev/input/event1: 0000 0000 00000000
    /dev/input/event1: 0001 0072 00000000 //(4)0是抬起
    /dev/input/event1: 0000 0000 00000000
    
    //(5)按下音量加按键,可以看到音量加键对应的按键键值是0x73
    /dev/input/event1: 0001 0073 00000001
    /dev/input/event1: 0000 0000 00000000
    /dev/input/event1: 0001 0073 00000000
    /dev/input/event1: 0000 0000 00000000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    上面可以看到节点的名称和某个按键在上层的键值。

    ② dumpsys input
    atom:/ # dumpsys input
    INPUT MANAGER (dumpsys input)
    
    Input Manager State:
      Interactive: false
      System UI Visibility: 0x8008
      Pointer Speed: 0
      Pointer Gestures Enabled: true
      Show Touches: false
      Pointer Capture Enabled: false
    
    Event Hub State: //(1)事件状态信息是主要关注的
      BuiltInKeyboardId: -2
      Devices: //(2)Devices里面的每个信息都是对应不同的节点信息
        -1: Virtual
          Classes: 0x40000023
          Path:  (3)关注Path字符串,就是节点的位置,这里是虚拟,不清楚具体意义
          Enabled: true
          Descriptor: a718a782d34bc767f4689c232d64d527998ea7fd
          Location:
          ControllerNumber: 0
          UniqueId: 
          Identifier: bus=0x0000, vendor=0x0000, product=0x0000, version=0x0000
          KeyLayoutFile: /system/usr/keylayout/Generic.kl
          KeyCharacterMapFile: /system/usr/keychars/Virtual.kcm
          ConfigurationFile:
          HaveKeyboardLayoutOverlay: false
          VideoDevice: 
        1: aw8624_haptic
          Classes: 0x00000200
          Path: /dev/input/event2
          Enabled: true
          Descriptor: 65195a4ab35c59e79bbba55177be90fc42ed3ae6
          Location:
          ControllerNumber: 0
          UniqueId:
          Identifier: bus=0x0000, vendor=0x0000, product=0x0000, version=0x0000
          KeyLayoutFile:
          KeyCharacterMapFile:
          ConfigurationFile:
          HaveKeyboardLayoutOverlay: false
          VideoDevice: 
        2: ACCDET
          Classes: 0x00000081
          Path: /dev/input/event0
          Enabled: true
          Descriptor: 1c78f7e0d16d4dbc8d3ab93943523f379203f90b
          Location:
          ControllerNumber: 0
          UniqueId:
          Identifier: bus=0x0019, vendor=0x0000, product=0x0000, version=0x0000
          KeyLayoutFile: /system/usr/keylayout/Generic.kl
          KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
          ConfigurationFile:
          HaveKeyboardLayoutOverlay: false
          VideoDevice: 
        3: fts_ts
          Classes: 0x00000015
          Path: /dev/input/event3
          Enabled: true
          Descriptor: a1cc21cba608c55d28d6dd2b1939004df0e0c756
          Location:
          ControllerNumber: 0
          UniqueId:
          Identifier: bus=0x0018, vendor=0x0000, product=0x0000, version=0x0000
          KeyLayoutFile: /system/usr/keylayout/Generic.kl
          KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
          ConfigurationFile:
          HaveKeyboardLayoutOverlay: false
          VideoDevice: 
    	  4: mtk-kpd //(4)按键事件的节点命名名称
          Classes: 0x00000001
          Path: /dev/input/event1 //(5)按键事件的节点位置,这个才是主要的,名称可以不看,但是节点必须找对
          Enabled: true
          Descriptor: f0d2e427e7a05eb6d316f5e14800c5ac7b6aee79
          Location:
          ControllerNumber: 0
          UniqueId:
          Identifier: bus=0x0019, vendor=0x2454, product=0x6500, version=0x0010 //(6)各版本号,寻找kl使用到
          KeyLayoutFile: /system/usr/keylayout/mtk-kpd.kl //(7)实际起作用的kl文件
          KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
          ConfigurationFile:
          HaveKeyboardLayoutOverlay: false
          VideoDevice: 
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    上面可以看到某个按键event节点的具体使能的kl和kcm文件。

    这里是使用的mtk-kpd.kl 文件,可以看到其他有些event使用的是默认的 Generic.kl 文件。

    二、选择过程分析

    1、选择kl的主要相关文件

    选择kl文件逻辑都是在cpp文件中的。

    framework\native\services\inputflinger\reader\EventHub.cpp
    framework\native\libs\input\InputDevice.cpp  //kl选择的主要逻辑
    framework\native\libs\input\Keyboard.cpp
    
    • 1
    • 2
    • 3

    2、kl文件选择主要流程

    这个要讲清楚还是比较麻烦的,这里只能介绍中间某一段。
    最开始哪里开始和最后哪里结束,还真不好介绍,因为本来对c不熟悉,这里都是强行看到逻辑代码。

    建议流程:

    	EventHub::getEvents 
    	-> EventHub::scanDevicesLocked 
    	-> EventHub::scanDirLocked 
    	-> EventHub::openDeviceLocked
    	-> EventHub::loadKeyMapLocked
    		-> Keyboard::KeyMap::load
    		-> KeyMap::loadKeyLayout
    		-> KeyMap.getPath(deviceIdentifier, name, InputDeviceConfigurationFileType::KEY_LAYOUT) //加载kl文件
    			-> InputDevice.getInputDeviceConfigurationFilePathByDeviceIdentifier //文件路径拼接和选择
    			-> InputDevice.getInputDeviceConfigurationFilePathByName //目录遍历,匹配kl
    		-> KeyMap::loadKeyLayout //未匹配到kl的情况,继续往下走
    		-> Keyboard.probeKeyMap(deviceIdentifier, "Generic") // 使用Generic.kl
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    不同的Android系统可能有差别,我看上面参考网址的是Android6的代码有些定义和现实是不一样的,但是总体流程差不多。

    其实懂得大致流程就ok了。具体代码需要自己添加日志打印查看流程,默认打印一点点。

    下面是主要过程代码分析:

    (1)EventHub 的分析
    static const char* DEVICE_INPUT_PATH = "/dev/input"; //系统input子系统目录,按键事件的节点都在这个目录下
    
    //1、这个方法系统系统和每次触发按键都会执行
    size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
        ALOG_ASSERT(bufferSize >= 1);
        ALOGI("getEvents "); //2、自己添加的打印,发现系统启动一次和每次按键触摸等事件都有打印
    	...
    	for (;;) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (mNeedToScanDevices) {
                mNeedToScanDevices = false; //3、设置只加载一次配置文件
                scanDevicesLocked();//4、扫描加载配置文件
                mNeedToSendFinishedDeviceScan = true;
            }
    	}
    	...
    }
    
    //5、扫描加载节点函数
    void EventHub::scanDevicesLocked() {
        status_t result;
        std::error_code errorCode;
    
        if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) {
            result = scanDirLocked(DEVICE_INPUT_PATH);//6、扫描目录 "/dev/input"
            if (result < 0) {
                ALOGE("scan dir failed for %s", DEVICE_INPUT_PATH);
            }
        } else {
            if (errorCode) {
                ALOGW("Could not run filesystem::exists() due to error %d : %s.", errorCode.value(),
                      errorCode.message().c_str());
            }
        }
    。。。
    }
    
    //7、扫描加载界面目录函数,有兴趣的可以打印下这个 entry.path() 是否某个某个具体的节点信息
    status_t EventHub::scanDirLocked(const std::string& dirname) {
        for (const auto& entry : std::filesystem::directory_iterator(dirname)) {
            openDeviceLocked(entry.path());
        }
        return 0;
    }
    
    
    //8、加载界面目录函数,里面具体的判断非常多,这里只展示主要部分
    void EventHub::openDeviceLocked(const std::string& devicePath) {
        // If an input device happens to register around the time when EventHub's constructor runs, it
        // is possible that the same input event node (for example, /dev/input/event3) will be noticed
        // in both 'inotify' callback and also in the 'scanDirLocked' pass. To prevent duplicate devices
        // from getting registered, ensure that this path is not already covered by an existing device.
    
        ALOGV("Opening device: %s", devicePath.c_str()); // 9、Android13 ALOGV是打印不出来的,如果需要查看信息可以修改为 ALOGI
    
    ...
    
        ALOGV("add device %d: %s\n", deviceId, devicePath.c_str()); //这里的ALOGV 也是同理,所以Android13 上加载流程日志是超少的
        ALOGV("  bus:        %04x\n"
              "  vendor      %04x\n"
              "  product     %04x\n"
              "  version     %04x\n",
              identifier.bus, identifier.vendor, identifier.product, identifier.version);
        ALOGV("  name:       \"%s\"\n", identifier.name.c_str());
    
        // Load the configuration file for the device.
        device->loadConfigurationLocked(); //10、这里是加载kcm配置文件的,有兴趣可以看,这里不去追
    
    	。。。
    
        // Configure virtual keys.
        if ((device->classes.test(InputDeviceClass::TOUCH))) {
            // Load the virtual keys for the touch screen, if any.
            // We do this now so that we can make sure to load the keymap if necessary.
            bool success = device->loadVirtualKeyMapLocked(); // 11、加载虚拟按键配置,这里也不去追
            if (success) {
                device->classes |= InputDeviceClass::KEYBOARD;
            }
        }
    
        // Load the key map.
        // We need to do this for joysticks too because the key layout may specify axes, and for
        // sensor as well because the key layout may specify the axes to sensor data mapping.
        status_t keyMapStatus = NAME_NOT_FOUND;
        if (device->classes.any(InputDeviceClass::KEYBOARD | InputDeviceClass::JOYSTICK |
                                InputDeviceClass::SENSOR)) {
            // Load the keymap for the device.
            keyMapStatus = device->loadKeyMapLocked(); //** 12、这里加载才是加载按键配置
        }
    
    
    }
    
    //13、这里调用的是 Keyboard::KeyMap::load 
    status_t EventHub::Device::loadKeyMapLocked() {
        return keyMap.load(identifier, configuration.get());
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    下面继续追踪Keyboard.cpp的代码。

    (2)Keyboard 的分析
    
    //1、加载配置
    status_t KeyMap::load(const InputDeviceIdentifier& deviceIdentifier,
            const PropertyMap* deviceConfiguration) {
        // Use the configured key layout if available.
        if (deviceConfiguration) {
            String8 keyLayoutName;
            if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
                    keyLayoutName)) {
                status_t status = loadKeyLayout(deviceIdentifier, keyLayoutName.c_str());//2、加载配置具体信息
                if (status == NAME_NOT_FOUND) {
                    ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
                            "it was not found.",
                            deviceIdentifier.name.c_str(), keyLayoutName.string());
                }
            }
    ...
        //      generic key map to use (US English, etc.) for typical external keyboards.
        if (probeKeyMap(deviceIdentifier, "Generic")) { //3、如果没找到kl文件,就是用 Generic.kl,这个文件正常是肯定存在的
            return OK;
        }
    
    }
    
    //4、加载配置具体信息
    status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
            const std::string& name) {
        std::string path(getPath(deviceIdentifier, name, InputDeviceConfigurationFileType::KEY_LAYOUT)); //5、getPath 函数获取路径
        if (path.empty()) {
            return NAME_NOT_FOUND;
        }
    
        base::Result> ret = KeyLayoutMap::load(path);
        if (ret.ok()) {
            keyLayoutMap = *ret;
            keyLayoutFile = path;
            return OK;
        }
    。。。
    }
    
    //6、getPath 获取路径函数
    static std::string getPath(const InputDeviceIdentifier& deviceIdentifier, const std::string& name,
                               InputDeviceConfigurationFileType type) {
        return name.empty()
                ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type) //7、里面包含目录遍历
                : getInputDeviceConfigurationFilePathByName(name, type); //8、具体目录的查找
    }
    
    
    //9、无论是目录遍历函数还是具体目录查找函数都是在 InputDevice 里面实现的,稍后追踪
    
    //10、上面load函数没找到kl的情况,调用的方法
    bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
            const std::string& keyMapName) {
        if (!haveKeyLayout()) {
            loadKeyLayout(deviceIdentifier, keyMapName); //11、最后还是调用了getPath,正常情况就是它了!
        }
        if (!haveKeyCharacterMap()) {
            loadKeyCharacterMap(deviceIdentifier, keyMapName);
        }
        return isComplete();
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    接上面9的点,继续往下追踪 InputDevice.cpp

    (3)InputDevice 的分析
    //1、目录遍历函数
    std::string getInputDeviceConfigurationFilePathByDeviceIdentifier(
            const InputDeviceIdentifier& deviceIdentifier, InputDeviceConfigurationFileType type,
            const char* suffix) {
        if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
            if (deviceIdentifier.version != 0) {
                // Try vendor product version.
    			//2、看看看这里:Vendor_X_Product_X_Version_X的查看
    			//3、getInputDeviceConfigurationFilePathByName 具体目录的查找后续分析
                std::string versionPath =
                        getInputDeviceConfigurationFilePathByName(StringPrintf("Vendor_%04x_Product_%"
                                                                               "04x_Version_%04x%s",
                                                                               deviceIdentifier.vendor,
                                                                               deviceIdentifier.product,
                                                                               deviceIdentifier.version,
                                                                               suffix),
                                                                  type);
                if (!versionPath.empty()) { //4、Version找到了就返回
                    return versionPath;
                }
            }
    
            // Try vendor product.
    		 //5、只找Vendor和Product的情况
            std::string productPath =
                    getInputDeviceConfigurationFilePathByName(StringPrintf("Vendor_%04x_Product_%04x%s",
                                                                           deviceIdentifier.vendor,
                                                                           deviceIdentifier.product,
                                                                           suffix),
                                                              type);
            if (!productPath.empty()) {
                return productPath;//6、Version找到了就返回
            }
        }
    
        // Try device name.
    	//7、Version和Vendor都找不到,就找节点命名名称
        return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName() + suffix,
                                                         type);
    }
    
    
    //8、具体目录的查找函数
    std::string getInputDeviceConfigurationFilePathByName(
            const std::string& name, InputDeviceConfigurationFileType type) {
        // Search system repository.
        std::string path;
    
        // Treblized input device config files will be located /product/usr, /system_ext/usr,
        // /odm/usr or /vendor/usr.
        // These files may also be in the com.android.input.config APEX.
        //9、看看看这里,根目录的遍历
    	const char* rootsForPartition[]{
                "/product",
                "/system_ext",
                "/odm",
                "/vendor",
                "/apex/com.android.input.config/etc",
                getenv("ANDROID_ROOT"), //10、这个是 system目录
        };
        for (size_t i = 0; i < size(rootsForPartition); i++) {
            if (rootsForPartition[i] == nullptr) {
                continue;
            }
            path = rootsForPartition[i];//11、遍历根目录
            path += "/usr/";//11、遍历根目录+usr
            appendInputDeviceConfigurationFileRelativePath(path, name, type);// 12、拼接path路径:path + name Vendor_xxx那些,type .文件后缀
    。。。
            return path;//
            }
        }
    
        // Search user repository.
        // TODO Should only look here if not in safe mode.//13、如果系统目录找不到,就用date下面的,这里也说了会不太安全
        path = "";
        char *androidData = getenv("ANDROID_DATA"); //14、根目录data
        if (androidData != nullptr) {
            path += androidData;
        }
        path += "/system/devices/"; //14、目录data/system/devices/
        appendInputDeviceConfigurationFileRelativePath(path, name, type);// 15、拼接path路径
    。。。
            return path;
    }
    
    // 16、拼接path路径函数
    static void appendInputDeviceConfigurationFileRelativePath(std::string& path,
            const std::string& name, InputDeviceConfigurationFileType type) {
    	//17、path 根目录 + CONFIGURATION_FILE_DIR 文件类型 + 文件名称 + 后缀
    	path += CONFIGURATION_FILE_DIR[static_cast(type)];
        path += name;
        path += CONFIGURATION_FILE_EXTENSION[static_cast(type)];
    }
    
    // 18、文件类型,不用的文件类型是放在不同的目录下的
    static const char* CONFIGURATION_FILE_DIR[] = {
            "idc/", //idc文件目录
            "keylayout/", //kl文件目录
            "keychars/", //kcm文件目录
    };
    
    // 19、后缀
    static const char* CONFIGURATION_FILE_EXTENSION[] = {
            ".idc",
            ".kl",
            ".kcm",
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    到这里基本代码是已经分析完成了,不清楚的可以自己看下具体代码了。代码挺多估计没几个人会仔细看!

    按键相关的代码已上传,有兴趣的可以下载查看:

    https://download.csdn.net/download/wenzhi20102321/88366484

    3、验证kl选择次序是否正常

    直接修改设备当前选择的kl文件名称,或者cp一份kl文件到优先级高的目录,需要重启才能生效。
    dumpsys input 查看某类按键键值当前使用的kl文件。

    比如上面 dumpsys input 的event1的信息:

      4: mtk-kpd //(1)按键事件的节点命名名称
          Classes: 0x00000001
          Path: /dev/input/event1 //(1)按键事件的节点位置,这个才是主要的,名称可以不看,但是节点必须找对
          Identifier: bus=0x0019, vendor=0x2454, product=0x6500, version=0x0010 //(3)各版本号,寻找kl使用到
          KeyLayoutFile: /system/usr/keylayout/mtk-kpd.kl //(4)实际起作用的kl文件
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    获取到有用的信息:

    event1 按键,节点在底层的名称是 mtk-kpd
    event1 按键的版本信息 Identifier:vendor=0x2454, product=0x6500, version=0x0010
    event1 按键的实际起作用的kl文件目录 KeyLayoutFile:/system/usr/keylayout/mtk-kpd.kl

    (1)复制kl文件到其他路径进行测试

    把 mtk-kpd.kl复制到 /product/usr/keylayout/

    重启后,dumpsys input ,查看event1 的信息可以看到,实际起作用的kl文件 :

     KeyLayoutFile: /product/usr/keylayout/mtk-kpd.kl
    
    • 1

    其他路径我就不一一测试了,有兴趣的可以尝试。

    需要注意的是,kl遍历的文件目录 除了 /system/usr/keylayout/这个目录是系统生成的,

    其他所有的目录我看了一下都是不存在的,除非你手动创建或者复制文件的时候创建。

    所以未特别适配kl文件的系统,kl文件基本都是在 /system/usr/keylayout/ 这个目录,并且里面是有非常多的没啥作用的kl文件。

    (2)复制一个version文件到同级目录下测试

    保存 /product/usr/keylayout/mtk-kpd.kl 的情况下,复制一个version文件到当前目录下
    Version文件: Vendor_0001_Product_0001_Version_0100.kl

    重启后,dumpsys input ,查看event1 的信息可以看到,实际起作用的kl文件 :

     KeyLayoutFile: /product/usr/keylayout/Vendor_0001_Product_0001_Version_0100.kl
    
    • 1

    到这里可以看到,无论是修改文件路径还是文件名称都是有作用的。

    (3)其他情况
    ①Vendor、Product、Version是 0000 的情况

    并不是所有的 Version 都有数值的,这种情况如果添加 Vendor_0000_Product_0000_Version_0000.kl
    或者 Vendor_0_Product_0_Version_0.kl 那么会选择优先选择这个文件吗?

    是不会选择Version_0000/0的,这个我是有测试过的。估计这里0只是没到的检测到Version的默认显示,并不是真正的字符串。

    ②Generic.kl 有在高优先级目录,会先选哪个?

    其实 Generic.kl 是最后无奈的选择!

    优先从所有目录下查看 Version文件和节点名称文件kl,如果没有找到最后在所有目录找一遍 Generic.kl 文件。

    如果没有Version文件和节点名称文件kl的情况,
    高优先级目录有 Generic.kl 文件就直接使用这个文件,不用再去 /system/usr/keylayout/ 查找。

    ③修改kl文件里面的内容进行测试

    上面都是在命令行查看实际使用的kl文件,但是想要测试是否真实准确,其实可以修改kl文件里面的内容确认。

    修改:/system/usr/keylayout/mtk-kpd.kl 
    
    key 115      VOLUME_UP //音量加
    key 114      VOLUME_UP //音量减,修改成音量加功能字符串
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现修改的方式可以pull文件后再push进去或者使用busybox vi XXX 功能,都是需要root权限的!

    这里只是测试验证功能,实际没啥这样改的场景哈。

    修改后重启设备测试效果,查看是否符合预期,复制到其他文件尝试。

    三、总结

    1、kl文件具体选择优先级:

    不同的Android系统选择可能不一样,这里以Android13上的代码进行举例:

    	"/product/usr/keylayout/name.kl",
    	"/system_ext/usr/keylayout/name.kl",
    	"/odm/usr/keylayout/name.kl",
    	"/vendor/usr/keylayout/name.kl",
    	"/apex/com.android.input.config/etc/usr/keylayout/name.kl",
    	"/system/usr/keylayout/name.kl"
    	"/data/system/devices/keylayout/name.kl"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    name.kl 名称的优先级:

    Vendor_4x_Product_4x_Version_4x.kl
    Vendor_4x_Product_4x.kl
    eventName.kl //节点在底层的命名名称
    Generic.kl
    
    • 1
    • 2
    • 3
    • 4

    名称name的优先级选择的代码在
    InputDevice.getInputDeviceConfigurationFilePathByDeviceIdentifier函数中体现;

    目录path优先级选择的代码在
    InputDevice.getInputDeviceConfigurationFilePathByName函数中体现。

    优先从所有目录下查看 Version文件和节点名称文件kl,
    如果没有找到最后在所有目录找一遍 Generic.kl 文件。

    2、其他

    定义eventX节点的名称是在内核代码中决定的,具体选择哪个kl文件的逻辑是系统framework中。

    并且可以在framework中进行修改kl文件选择的顺序。

    这些都是Android系统的流程,正常情况有个了解就行了,一般不需要修改,
    但是如果真的出现问题或有相关修改需求还是要分析适配的。

  • 相关阅读:
    LeetCode220730_62、位1的个数
    sort 排序使用介绍
    HC32_HC32F072FAUA_内部温度传感器+外部输入_ADC多通道采集
    若依框架学习笔记
    周老师话职称(陕西省职称申报好处)
    VMware Workstation Pro 16、CentOS7镜像 (Windows10)下载安装
    .NET性能优化-快速遍历List集合
    Python的requests库使用总结
    OpenGL编程学习笔记——glBegin
    【自动驾驶】PETR/PETRv2/StreamPETR论文分析
  • 原文地址:https://blog.csdn.net/wenzhi20102321/article/details/133183775