会议平板的按键加载会在多个不同地方注册及上报按键,需要逐个去解析。
- 遥控
- 五向
- 广播
一.从遥控器获得按键
1.设备对应keylayout映射文件
1) 布局文件保存路径
遥控器的KeyMap具体kl文件保存路径为device/whaley/generic/prebuilts/keylayout目录下保存。kl文件的保存格式为Vendor_[vendorID]_Product_[productID].kl
** (e.g.) Vendor_2ba5_Product_0882.kl**
查看蓝牙设备的Vendor ID和Product ID
adb shell cat /proc/bus/input/devices
(e.g.)
I: Bus=0005 Vendor=2ba5 Product=0882 Version=0100
N: Name="微鲸遥控器"
P: Phys=
S: Sysfs=/devices/virtual/misc/uhid/0005:2BA5:0882.0005/input/input9
U: Uniq=dc:2c:26:fa:6e:a2
H: Handlers=sysrq kbd event5
B: PROP=0
B: EV=12001b
B: KEY=1000002000087 ff9f387ad9415fff febeffdfffefffff fffffffffffffffe
B: ABS=10000000000
B: MSC=10
B: LED=1f
2) 源码按键布局选择
开始从frameworks/native/libs/input/InputDevice.cpp
中的getInputDeviceConfigurationFilePathByDeviceIdentifier
方法得到布局位置
String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
const InputDeviceIdentifier& deviceIdentifier,
InputDeviceConfigurationFileType type) {
if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
if (deviceIdentifier.version != 0) {
// Try vendor product version.
String8 versionPath(getInputDeviceConfigurationFilePathByName(
String8::format("Vendor_%04x_Product_%04x_Version_%04x",
deviceIdentifier.vendor, deviceIdentifier.product,
deviceIdentifier.version),
type));
if (!versionPath.isEmpty()) {
return versionPath;
}
}
// Try vendor product.
String8 productPath(getInputDeviceConfigurationFilePathByName(
String8::format("Vendor_%04x_Product_%04x",
deviceIdentifier.vendor, deviceIdentifier.product),
type));
if (!productPath.isEmpty()) {
return productPath;
}
}
// Try device name.
return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
}
从productPath方法得到的按键布局位置/system/usr/keylayout/
,再通过frameworks/native/services/inputflinger/EventHub.cpp
中的loadConfigurationLocked
去解析文件内容
void EventHub::loadConfigurationLocked(Device* device) {
device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
if (device->configurationFile.isEmpty()) {
ALOGD("No input device configuration file found for device '%s'.",
device->identifier.name.string());
} else {
status_t status = PropertyMap::load(device->configurationFile,
&device->configuration);
if (status) {
ALOGE("Error loading input device configuration file for device '%s'. "
"Using default configuration.",
device->identifier.name.string());
}
}
}
3) 按键布局文件解读
Android配置蓝牙键值的步骤和一般的键值基本相同,但是有几点区别需要注意。我们在驱动层用多个红外键值对应了一个Android键值,在应用层靠KeyEvent.getScanCode()函数获取linux键值来加以区分。红外这么做已经比较熟,有方案商提供的文档。但是蓝牙用的是Linux默认的一套流程,需要花时间去搞。
首先先看下kl文件的差别
(e.g.)
#格式为基本key event上报
key 103 DPAD_UP
#格式为Bluetooth key event 上报 第一列表示这是一个键值,第二列可以先不管,第三列是蓝牙键值,第四列是Android键名
key usage 0x000700ea WHALEYMIC
2.会议平板的键值上报策略
并非通过event上报遥控器键值,经过跟踪源码发现而是通过驱动层直接回调方法给FWK层,去模拟按键触发,接下来一起看下按键流程吧。
按键流程是一致的但是调用的方法不同,都是异曲同工,这里就介绍分享语音键的上层上报流程
1) 设置驱动层BT监听注册回调
驱动层device/hisilicon/bigfish/bluetooth/[蓝牙设备号]/bluedroid/btif/src/btif_hh.c
实现注册监听
static bt_status_t init( bthh_callbacks_t* callbacks )
{
UINT32 i;
BTIF_TRACE_EVENT("%s", __FUNCTION__);
bt_hh_callbacks = callbacks;
memset(&btif_hh_cb, 0, sizeof(btif_hh_cb));
for (i = 0; i < BTIF_HH_MAX_HID; i++){
btif_hh_cb.devices[i].dev_status = BTHH_CONN_STATE_UNKNOWN;
}
/* Invoke the enable service API to the core to set the appropriate service_id */
btif_enable_service(BTA_HID_SERVICE_ID);
return BT_STATUS_SUCCESS;
}
2) Native层与kernel层交互
com_android_bluetooth_hid.cpp
Native层注册按键回调,创建sBluetoothHidInterface
接口并初始化sBluetoothHidCallbacks
static void initializeNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
return;
}
if (sBluetoothHidInterface !=NULL) {
ALOGW("Cleaning up Bluetooth HID Interface before initializing...");
sBluetoothHidInterface->cleanup();
sBluetoothHidInterface = NULL;
}
if (mCallbacksObj != NULL) {
ALOGW("Cleaning up Bluetooth GID callback object");
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = NULL;
}
if ( (sBluetoothHidInterface = (bthh_interface_t *)
btInf->get_profile_interface(BT_PROFILE_HIDHOST_ID)) == NULL) {
ALOGE("Failed to get Bluetooth HID Interface");
return;
}
if ( (status = sBluetoothHidInterface->init(&sBluetoothHidCallbacks)) != BT_STATUS_SUCCESS) {
ALOGE("Failed to initialize Bluetooth HID, status: %d", status);
sBluetoothHidInterface = NULL;
return;
}
mCallbacksObj = env->NewGlobalRef(object);
}
与JNI与JAVA赋值
method_onAudioStateChanged = env->GetMethodID(clazz, "onAudioStateChanged", "([BII)V");
回调Java的方法
static void audio_state_callback(bt_bdaddr_t *bd_addr, bthh_audio_state_t state,
bthh_audio_type_t audio_type) {
jbyteArray addr;
CHECK_CALLBACK_ENV
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr for HID channel state");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, addr, (jint) state,
(jint) audio_type);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
3) FWK层方法调用
HidService.java
调用onAudioStateChanged的方法启动handle实现模拟按键的操作
final long downTime = SystemClock.uptimeMillis();
KeyEvent down = KeyEvent.obtain(downTime, downTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_WHALEYMIC, 0, 0,KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD, null);
InputManager.getInstance().injectInputEvent(down, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
down.recycle();
二.从五向键获得按键
待更新
三.通过广播来下发按键
上层的按键逻辑下发并不是全部由系统按键流程下发,会议平板会做一层的监听,但不会拦截,在InputManagerService类的interceptKeyBeforeQueueing的系统拦截处增加的一个监听器,应用需要在监听器中注册,从而得到相关按键上报.
应用注册的统一入口,得到InputManagerService服务,注册按键监听器InputManager.java
public void registerListener(IWhaleyKeyListener listener, String id, boolean reg) {
try {
mIm.registerListener(listener, id, reg);
}
catch (RemoteException ex) {
Log.w(TAG, "Failed to registerListener", ex);
}
}
注册监听位置InputManagerService.java
private RemoteCallbackList<IWhaleyKeyListener> mCallbacks;
//由InputManager传递,处理其他应用的监听入口,需要参数监听器实例,记录索引的ID约定为应用名ID,boolean是否监听或取消监听
@Override
public void registerListener(IWhaleyKeyListener listener, String id, boolean reg) {
synchronized (mCallbacks) {
if(reg && listener != null) {
mCallbacks.register(listener, id);
}
else if(!reg && listener != null){
mCallbacks.unregister(listener);
}
}
}
监听的回调
private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
globalCallBack(event, policyFlags);
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
}
枚举监听者发送得到的按键键值
private int globalCallBack(KeyEvent event, int policyFlags) {
int ret = 0;
synchronized (mCallbacks) {
if(mCallbacks.getRegisteredCallbackCount() > 0) {
int n = mCallbacks.beginBroadcast();
try {
if(n > 0) {
for (int i = 0; i < n; i++) {
String id = (String)mCallbacks.getBroadcastCookie(i);
ret = mCallbacks.getBroadcastItem(i).notify(event, String.valueOf(policyFlags));
//Log.d(TAG, "#mCallbacks.notify:" + id);
}
}
} catch (Exception e) {
e.printStackTrace();
}
mCallbacks.finishBroadcast();
}
}
return ret;
}
(e.g.)应用的注册按键监听的方式
private InputManager mInputManager;
private MyKeyListener mMyKeyListener;
mMyKeyListener = new MyKeyListener();
mInputManager = InputManager.getInstance();
mInputManager.registerListener(mMyKeyListener, this.getPackageName(), true);
private class MyKeyListener extends IWhaleyKeyListener.Stub {
public MyKeyListener() {
}
@Override
public int notify(KeyEvent event, String extra) throws RemoteException {
.......
return 0;
}
}