Android USB耳机检测

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

 

[java] view plaincopy
 
  1. try {  
  2.     Slog.i(TAG, "Wired Accessory Observer");  
  3.     // Listen for wired headset changes  
  4.     new WiredAccessoryObserver(context);  
  5. catch (Throwable e) {  
  6.     reportWtf("starting WiredAccessoryObserver", e);  
  7. }  

 

class WiredAccessoryObserver extends UEventObserver,WiredAccessoryObserver继承自UEventObserver。

 

首先看构造函数:

[java] view plaincopy
 
  1. public WiredAccessoryObserver(Context context) {  
  2.     mContext = context;  
  3.     PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);  
  4.     mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver");  
  5.     mWakeLock.setReferenceCounted(false);  
  6.     mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);  
  7.   
  8.     context.registerReceiver(new BootCompletedReceiver(),  
  9.         new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);  
  10. }  

 

 

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

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

 

[java] view plaincopy
 
  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()函数:

[java] view plaincopy
 
  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()继续上报:

[java] view plaincopy
 
  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)

[java] view plaincopy
 
  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中发送一个消息,通过异步的方式将耳机状态报告给系统:

[java] view plaincopy
 
  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让系统进入睡眠。

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

 

感谢 : http://blog.csdn.net/tothesea/article/details/8989833

 

posted @ 2014-12-18 23:35  MMLoveMeMM  阅读(2186)  评论(0编辑  收藏  举报