最近有一个需求,就是在Android TV 上加一个全局的按键。下面来用一个例子一步一步说明从底层到上层的添加步骤
例如新加一个youtube 全局按键。(即按此键后会打开youtube app)
./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 在数组中加入 这行
kl文件的作用是把底层传上来的键值映射为Android键值,一般情况下,文件内容如下
key 233 CUSTOM_KEYCODE
第一列表示 这行是普通的键值
第二列表示 是linux 键值,即scancode
第三列表示 android 键名
或者
key usage 0x0c0073 YOUTUBE
kl文件的命名是有规律可循的,每一个设备都会有一个Vendor ID 和一个Product ID 这是由设备厂商命名的,一般来说文件会被命名为如下所示内容,注意最后的Product ID:xxxx不能是0
Vendor_xxxx_Product_xxxx.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文件可以通过device.mk文件直接配置到系统中,具体的代码类似如下
PRODUCT_COPY_FILES += $(DEVICE_FOLDER)/configs/Vendor_000d_Product_0001.kl:/system/usr/keylayout/Vendor_000d_Product_0001.kl
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
/** 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键值
这个文件主要是配置按键以及接收按键实现的类,其实就是当按了此按键之后,会发送一个全局广播到这个类中。然后这个类接受广播就可以去实现这个按键的功能了。
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。
接下来当然是广播的接收,在上层应用中接收
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)
这个命令可以查看到输入设备映射到了哪一个kl文件 kl文件映射
这个命令可以查看到输入的具体键值时多少
但是输出的是16进制。
模拟键值输入:
input keyevent android键值 例如:
input keyevent 20
注意这里的android 键值就是前面KeyEvent.java 里面对应的键值
参考:Android键值添加和调试