UEvent机制在Android中的应用,就我所知,USB的插拔和耳机的插拔检测都是通过UEvent来实现的。下面的例子,首先说明代码中是如何实现检测的,后面的文章再详细说明UEvent机制。
在Android4.0以上的版本,耳机检测的源文件位于frameworks/base/services/java/com/android/server/WiredAccessoryObserver.java,在android4.0以前是HeadsetObserver.java。从名字可以看出,它主要是用来检测有线的设备连接状态。
USB也是有线设备,但它的检测代码是独立的,位于frameworks/base/services/java/com/android/server/usb/usbdevicemanager.java。
首先,来看耳机检测的机制。
在WiredAccessoryObserver中,主要检测以下几个设备的连接状态(参考函数makeObservedUEventList(),其实就是生成要检测的设备文件节点路径)
1.headset
2.usb_headset
3.hdmi_audio/hdmi
都是与audio相关的设备,一般来说,headset都是支持的,后面的两种设备不是所有平台都支持。
从代码路径可以知道,位于service目录,因此可以猜想它是在android system server初始化的时候实例化的。在system server的serverthread 的run()函数中有如下代码:
- try {
- Slog.i(TAG, "Wired Accessory Observer");
- // Listen for wired headset changes
- new WiredAccessoryObserver(context);
- } catch (Throwable e) {
- reportWtf("starting WiredAccessoryObserver", e);
- }
class WiredAccessoryObserver extends UEventObserver,WiredAccessoryObserver继承自UEventObserver。
首先看构造函数:
- public WiredAccessoryObserver(Context context) {
- mContext = context;
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver");
- mWakeLock.setReferenceCounted(false);
- mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
- context.registerReceiver(new BootCompletedReceiver(),
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
- }
从上面的代码中似乎没有看到与耳机检测相关的代码,只是初始化了wakelock用于电源管理,获取audiomanager接口,然后注册了一个广播接收器,用于监听ACTION_BOOT_COMPLETED。
ACTION_BOOT_COMPLETED在系统启动完成后发出,收到intent后:
- private final class BootCompletedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- // At any given time accessories could be inserted
- // one on the board, one on the dock and one on HDMI:
- // observe three UEVENTs
- init(); // set initial status
- for (int i = 0; i < uEventInfo.size(); ++i) {
- UEventInfo uei = uEventInfo.get(i);
- startObserving("DEVPATH="+uei.getDevPath());
- }
- }
- }
在init函数中,会首先判断要检测的设备是否已经处于连接状态,因为有可能开机之前这些设备就连接上了。如果已经连接上就调用updateState()立即向系统上报。
然后调用startObserving()开始监测文件节点路径是否有状态变化,代码位于UEventObserver.java中。这里的处理就是监听路径的事件,如果kernel层有uevent事件发送上来则会去匹配这个路径字符串,如果匹配成功会调用在WiredAccessoryObserver重载的onEvent()函数:
- @Override
- public void onUEvent(UEventObserver.UEvent event) {
- if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
- try {
- String devPath = event.get("DEVPATH");
- String name = event.get("SWITCH_NAME");
- int state = Integer.parseInt(event.get("SWITCH_STATE"));
- updateState(devPath, name, state);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Could not parse switch state from event " + event);
- }
- }
以插入有线耳机为例,打印出的log为V/WiredAccessoryObserver( 440): Headset UEVENT: {SUBSYSTEM=switch, SWITCH_STATE=1, DEVPATH=/devices/virtua/switch/h2w, SEQNUM=2224, ACTION=change, SWITCH_NAME=Headset}
从event string中解析出devPath,name和state的值,从上面的log中很容易看出,然后继续调用updateState()继续上报:
- private synchronized final void updateState(String devPath, String name, int state)
- {
- for (int i = 0; i < uEventInfo.size(); ++i) {
- UEventInfo uei = uEventInfo.get(i);
- if (devPath.equals(uei.getDevPath())) {
- update(name, uei.computeNewHeadsetState(mHeadsetState, state));
- return;
- }
- }
- }
在这里要介绍一下computeNewHeadsetState(mHeadsetState, state)
- public int computeNewHeadsetState(int headsetState, int switchState) {
- int preserveMask = ~(mState1Bits | mState2Bits);
- int setBits = ((switchState == 1) ? mState1Bits :
- ((switchState == 2) ? mState2Bits : 0));
- return ((headsetState & preserveMask) | setBits);
- }
- }
可见,android是支持3-PIN和4-PIN耳机区分的,也就是带MIC和不带MIC的耳机。
后面的流程就很清晰了,update中会往handler中发送一个消息,通过异步的方式将耳机状态报告给系统:
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
- mWakeLock.release();
- }
- };
最终调用mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);将消息交给audio系统,audio系统收到后会向系统广播耳机已经插入的intent,同时会通知audiopolicy做audio通路切换的工作。这部分的内容在这里就不做讨论了。有兴趣的话可以继续跟踪调用流程。
这里要说一个之前碰到的bug,在高通平台上,耳机通话屏灭的时候,AP这一侧进入睡眠状态,此时拔出耳机,会概率性发生通话无声的问题。后来定位到是耳机拔出intent还没有广播系统就再次进入睡眠导致通路没有切换,依然停留在耳机通路。修改方法是将handler中的mWakeLock.release()改为mWakeLock.acquire(5000)。锁住唤醒状态5秒,在5秒超时后自动释放wakelock让系统进入睡眠。
总的来说,耳机的检测还是很简单的一个流程,主要是驱动中要生成相应的节点,如果要支持耳机类型检测,还需要修改驱动来支持。
感谢 : http://blog.csdn.net/tothesea/article/details/8989833