Android 耳机检测

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让系统进入睡眠。

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

你可能感兴趣的:(Android-Sys)