android系统中如何从上到下去添加一个全局按键(详细步骤)

最近有一个需求,就是在Android TV 上加一个全局的按键。下面来用一个例子一步一步说明从底层到上层的添加步骤
例如新加一个youtube 全局按键。(即按此键后会打开youtube app)

1. ./frameworks/native/include/input/InputEventLabels.h
    DEFINE_KEYCODE(COPY),
    DEFINE_KEYCODE(PASTE),
    DEFINE_KEYCODE(SYSTEM_NAVIGATION_UP),
    DEFINE_KEYCODE(SYSTEM_NAVIGATION_DOWN),
    DEFINE_KEYCODE(SYSTEM_NAVIGATION_LEFT),
    DEFINE_KEYCODE(SYSTEM_NAVIGATION_RIGHT),
    DEFINE_KEYCODE(ALL_APPS),
    DEFINE_KEYCODE(YOUTUBE),     //  1 在数组中加入 这行
2. kl文件

kl文件的作用是把底层传上来的键值映射为Android键值,一般情况下,文件内容如下

key     233    CUSTOM_KEYCODE
第一列表示 这行是普通的键值
第二列表示 是linux 键值,即scancode
第三列表示 android 键名

或者

key usage 0x0c0073 YOUTUBE
kl文件的命名

kl文件的命名是有规律可循的,每一个设备都会有一个Vendor ID 和一个Product ID 这是由设备厂商命名的,一般来说文件会被命名为如下所示内容,注意最后的Product ID:xxxx不能是0

Vendor_xxxx_Product_xxxx.kl
kl文件的优先级

按照Android标准,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  

一般来说kl文件由厂家提供,但是有一些无良厂家不想修改,如果键值不冲突,可以直接在Generic.kl文件中更改。

Generic.kl文件在代码中的位置为

./framewprk/base/data/keyboards/Generic.kl
添加自定义的kl文件

kl文件可以通过device.mk文件直接配置到系统中,具体的代码类似如下

PRODUCT_COPY_FILES += $(DEVICE_FOLDER)/configs/Vendor_000d_Product_0001.kl:/system/usr/keylayout/Vendor_000d_Product_0001.kl
3.keycodes.h文件
frameworks / native/include/android/keycodes.h

    AKEYCODE_SYSTEM_NAVIGATION_UP = 280,
    /** fingerprint navigation key, down. */
    AKEYCODE_SYSTEM_NAVIGATION_DOWN = 281,
    /** fingerprint navigation key, left. */
    AKEYCODE_SYSTEM_NAVIGATION_LEFT = 282,
    /** fingerprint navigation key, right. */
    AKEYCODE_SYSTEM_NAVIGATION_RIGHT = 283,
    /** all apps */
    AKEYCODE_ALL_APPS = 284,
    
    AKEYCODE_YOUTUBE = 402,      //这个就是android 中的键值 注意前面有个 AKEYCODE

4. KeyEvent.java
    /** Key code constant: Directional Pad Up-Right */
    public static final int KEYCODE_DPAD_UP_RIGHT   = 270;
    /** Key code constant: Directional Pad Down-Right */
    public static final int KEYCODE_DPAD_DOWN_RIGHT = 271;
    /** Key code constant: Skip forward media key. */
    public static final int KEYCODE_MEDIA_SKIP_FORWARD = 272;
    /** Key code constant: Skip backward media key. */
    public static final int KEYCODE_MEDIA_SKIP_BACKWARD = 273;
    /** Key code constant: Step forward media key.
     * Steps media forward, one frame at a time. */
    public static final int KEYCODE_MEDIA_STEP_FORWARD = 274;
    /** Key code constant: Step backward media key.
     * Steps media backward, one frame at a time. */
    public static final int KEYCODE_MEDIA_STEP_BACKWARD = 275;
    /** Key code constant: put device to sleep unless a wakelock is held. */
    public static final int KEYCODE_SOFT_SLEEP = 276;
    /** Key code constant: Cut key. */
    public static final int KEYCODE_CUT = 277;
    /** Key code constant: Copy key. */
    public static final int KEYCODE_COPY = 278;
    /** Key code constant: Paste key. */
    public static final int KEYCODE_PASTE = 279;
    /** Key code constant: Consumed by the system for navigation up */
    public static final int KEYCODE_SYSTEM_NAVIGATION_UP = 280;
    /** Key code constant: Consumed by the system for navigation down */
    public static final int KEYCODE_SYSTEM_NAVIGATION_DOWN = 281;
    /** Key code constant: Consumed by the system for navigation left*/
    public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
    /** Key code constant: Consumed by the system for navigation right */
    public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
    /** Key code constant: Show all apps
     * @hide */
    public static final int KEYCODE_ALL_APPS = 284;

    public static final int KEYCODE_YOUTUBE = 402;   这个是最上层的android键值
5.global_keys.xml 文件配置

    
    

这个文件主要是配置按键以及接收按键实现的类,其实就是当按了此按键之后,会发送一个全局广播到这个类中。然后这个类接受广播就可以去实现这个按键的功能了。

6. framework中解析xml 中发送按键的广播
frameworks / base/services/core/java/com/android/server/policy/GlobalKeyManager.java

final class GlobalKeyManager {
    private static final String TAG = "GlobalKeyManager";

    private static final String TAG_GLOBAL_KEYS = "global_keys";
    private static final String ATTR_VERSION = "version";
    private static final String TAG_KEY = "key";
    private static final String ATTR_KEY_CODE = "keyCode";
    private static final String ATTR_COMPONENT = "component";

    private static final int GLOBAL_KEY_FILE_VERSION = 1;

    private SparseArray mKeyMapping;

    public GlobalKeyManager(Context context) {
        mKeyMapping = new SparseArray();
        loadGlobalKeys(context);
    }

    /**
     * Broadcasts an intent if the keycode is part of the global key mapping.
     *
     * @param context context used to broadcast the event
     * @param keyCode keyCode which triggered this function
     * @param event keyEvent which trigged this function
     * @return {@code true} if this was handled
     */
    boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) {   // 发送广播,将键值存入intent 中
        if (mKeyMapping.size() > 0) {
            ComponentName component = mKeyMapping.get(keyCode);
            if (component != null) {
                Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON)
                        .setComponent(component)
                        .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                        .putExtra(Intent.EXTRA_KEY_EVENT, event);
                context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null);
                return true;
            }
        }
        return false;
    }

    /**
     * Returns {@code true} if the key will be handled globally.
     */
    boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) {
        return mKeyMapping.get(keyCode) != null;
    }

    private void loadGlobalKeys(Context context) {
        XmlResourceParser parser = null;
        try {
            parser = context.getResources().getXml(com.android.internal.R.xml.global_keys);  // 解析xml 并将键和包名放入map 中
            XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS);
            int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0);
            if (GLOBAL_KEY_FILE_VERSION == version) {
                while (true) {
                    XmlUtils.nextElement(parser);
                    String element = parser.getName();
                    if (element == null) {
                        break;
                    }
                    if (TAG_KEY.equals(element)) {
                        String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE);
                        String componentName = parser.getAttributeValue(null, ATTR_COMPONENT);
                        int keyCode = KeyEvent.keyCodeFromString(keyCodeName);
                        if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
                            mKeyMapping.put(keyCode, ComponentName.unflattenFromString(
                                    componentName));
                        }
                    }
                }
            }
        } catch (Resources.NotFoundException e) {
            Log.w(TAG, "global keys file not found", e);
        } catch (XmlPullParserException e) {
            Log.w(TAG, "XML parser exception reading global keys file", e);
        } catch (IOException e) {    
            Log.w(TAG, "I/O exception reading global keys file", e);
        } finally {
            if (parser != null) {
                parser.close();
            }
        }
    }

上面handleGlobalKey 在PhoneWindowManager 的interceptKeyBeforeDispatching方法中调用,拿到keycode 后先后判断有没有在全局globe xml 中定义,如有则返回true,如没有,则返回false。

7. 按键功能实现

接下来当然是广播的接收,在上层应用中接收

public class NetflixBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.v(TAG, "onReceive, " + intent);
		
 if (Intent.ACTION_GLOBAL_BUTTON.equals(intent.getAction())) {      //收到广播
            NetflixLaunchService.sendBroadcastForStopTKDvrRecord(context);
            KeyEvent localKeyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
            int keycode = localKeyEvent.getKeyCode();

            if (keycode == KeyEvent.KEYCODE_FUNCTION) {
                keycode = localKeyEvent.getScanCode();
            }

            handleInputKey(context, keycode, localKeyEvent);
        }
    }

    private void handleInputKey(Context context, int keycode, KeyEvent event) {
        if (event.getRepeatCount() > 0) {
            Log.d(TAG, "repeat count.");
            return;
        }
	 if (keycode == KeyEvent.KEYCODE_YOUTUBE) {
	String REMOTE_YT_BUTTON = "ro.product.brand.property_yt_remote_button";
        Intent youtube_intent = new Intent().setPackage("com.google.android.youtube.tv").putExtra(REMOTE_YT_BUTTON, true);
        try {
            context.startActivity(youtube_intent);      //打开youtube
        }catch (ActivityNotFoundException e){
            e.printStackTrace();
        }
        } 	 

当然 ACTION_GLOBAL_BUTTON 还得在清单文件中注册一下。

到此,一个全局按键就添加完成。

这里再补充一下几个用于串口调试按键的命令:getevent ,dumpsys input,input keyevent
(当然如果再adb下,就要加adb shell)

dumpsys input

这个命令可以查看到输入设备映射到了哪一个kl文件 kl文件映射

getevent

这个命令可以查看到输入的具体键值时多少
但是输出的是16进制。

input keyevent

模拟键值输入:

input keyevent android键值 例如:

input keyevent 20

注意这里的android 键值就是前面KeyEvent.java 里面对应的键值

参考:Android键值添加和调试

你可能感兴趣的:(Android,系统,linux)