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()函数中有如下代码:

 



1. try {  
2. "Wired Accessory Observer");  
3. // Listen for wired headset changes  
4. new WiredAccessoryObserver(context);  
5. } catch (Throwable e) {  
6. "starting WiredAccessoryObserver", e);  
7. }


 

class WiredAccessoryObserver extends UEventObserver,WiredAccessoryObserver继承自UEventObserver。

 

首先看构造函数:



1. public WiredAccessoryObserver(Context context) {  
2.     mContext = context;  
3.     PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);  
4. "WiredAccessoryObserver");  
5. false);  
6.     mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);  
7.   
8. new BootCompletedReceiver(),  
9. new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);  
10. }



 

 

从上面的代码中似乎没有看到与耳机检测相关的代码,只是初始化了wakelock用于电源管理,获取audiomanager接口,然后注册了一个广播接收器,用于监听ACTION_BOOT_COMPLETED。

ACTION_BOOT_COMPLETED在系统启动完成后发出,收到intent后:

 



1. private final class BootCompletedReceiver extends BroadcastReceiver {  
2. @Override  
3. public void onReceive(Context context, Intent intent) {  
4. // At any given time accessories could be inserted  
5. // one on the board, one on the dock and one on HDMI:  
6. // observe three UEVENTs  
7. init(); // set initial status  
8. for (int i = 0; i < uEventInfo.size(); ++i) {  
9. UEventInfo uei = uEventInfo.get(i);  
10. startObserving("DEVPATH="+uei.getDevPath());  
11. }  
12. }  
13. }



在init函数中,会首先判断要检测的设备是否已经处于连接状态,因为有可能开机之前这些设备就连接上了。如果已经连接上就调用updateState()立即向系统上报。

然后调用startObserving()开始监测文件节点路径是否有状态变化,代码位于UEventObserver.java中。这里的处理就是监听路径的事件,如果kernel层有uevent事件发送上来则会去匹配这个路径字符串,如果匹配成功会调用在WiredAccessoryObserver重载的onEvent()函数:



1. @Override  
2. public void onUEvent(UEventObserver.UEvent event) {  
3. if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());  
4.   
5. try {  
6. String devPath = event.get("DEVPATH");  
7. String name = event.get("SWITCH_NAME");  
8. int state = Integer.parseInt(event.get("SWITCH_STATE"));  
9. updateState(devPath, name, state);  
10. } catch (NumberFormatException e) {  
11. Slog.e(TAG, "Could not parse switch state from event " + event);  
12. }  
13. }



以插入有线耳机为例,打印出的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()继续上报:



1. private synchronized final void updateState(String devPath, String name, int state)  
2. {  
3. for (int i = 0; i < uEventInfo.size(); ++i) {  
4. UEventInfo uei = uEventInfo.get(i);  
5. if (devPath.equals(uei.getDevPath())) {  
6. update(name, uei.computeNewHeadsetState(mHeadsetState, state));  
7. return;  
8. }  
9. }  
10. }




在这里要介绍一下computeNewHeadsetState(mHeadsetState, state)



1. public int computeNewHeadsetState(int headsetState, int switchState) {  
2. int preserveMask = ~(mState1Bits | mState2Bits);  
3. int setBits = ((switchState == 1) ? mState1Bits :  
4. ((switchState == 2) ? mState2Bits : 0));  
5.   
6. return ((headsetState & preserveMask) | setBits);  
7. }  
8. }



可见,android是支持3-PIN和4-PIN耳机区分的,也就是带MIC和不带MIC的耳机。

后面的流程就很清晰了,update中会往handler中发送一个消息,通过异步的方式将耳机状态报告给系统:



1. private final Handler mHandler = new Handler() {  
2. @Override  
3. public void handleMessage(Message msg) {  
4. setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);  
5. mWakeLock.release();  
6. }  
7. };



最终调用mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);将消息交给audio系统,audio系统收到后会向系统广播耳机已经插入的intent,同时会通知audiopolicy做audio通路切换的工作。这部分的内容在这里就不做讨论了。有兴趣的话可以继续跟踪调用流程。

这里要说一个之前碰到的bug,在高通平台上,耳机通话屏灭的时候,AP这一侧进入睡眠状态,此时拔出耳机,会概率性发生通话无声的问题。后来定位到是耳机拔出intent还没有广播系统就再次进入睡眠导致通路没有切换,依然停留在耳机通路。修改方法是将handler中的mWakeLock.release()改为mWakeLock.acquire(5000)。锁住唤醒状态5秒,在5秒超时后自动释放wakelock让系统进入睡眠。

总的来说,耳机的检测还是很简单的一个流程,主要是驱动中要生成相应的节点,如果要支持耳机类型检测,还需要修改驱动来支持。