最近遇到一个bug: 当播放音乐时,插拔耳机,此时闹钟响起,闹钟响铃没有声音。
刚开始以为是闹钟问题,浪费了不少时间,后来发现,插拔耳机后,音乐播放也会出现没有声音。
使用命令,
adb shell getevent 发现,插拔耳机时,底层事件有正常上报。
adb shell parameter status > status.log 查看,log中,当耳机拔出后,还是HeadSet状态,此时扬声器模式是disabled的,所以没有声音,再次插入耳机就有声音了。log属于特定打印,不同机子不一样。
SelectedInputDevice = <none> SelectedOutputDevice = Headset
因此,特地代码跟踪了一下耳机处理流程。 参考了几篇大牛们的博客。
代码版本主要以Android 4.4和Android 5.0的为主。
主要有两种处理方式、
Android 系统默认是使用UEvent的方式。两种模式的切换可以使用配置项来完成。
在:android4.4/frameworks/base/core/res/res/values/config.xml 中有
config.xml:1151: <bool name="config_useDevInputEventForAudioJack">false</bool> 配置项。当为false 则使用UEVent方式,为true则使用InputEvent方式,现在大多使用后者,具体得看厂商的定制代码。
比如我这里的device目录下,配置为true,使用的InputEvent方式。
xxx_rtfsc@xxx-HP-EliteDesk-880-G1-TWR:~/rtfsc/xxxxx_5.0/device$ grep -rn config_useDevInputEventForAudioJack xxxx/common/overlays_aosp/frameworks/base/core/res/res/values/config.xml:40: <bool name="config_useDevInputEventForAudioJack">true</bool>
一 InputEvent
这种方式主要InputManagerService.java(framework/base/services/java/com/android/server/input/InputManagerService.java)中处理。
在InputManagerService的构造函数中,获取config_useDevInputEventForAudioJack的值,判断当前是否是使用InputEvent模式。
//判断当前是否使用InputEvent实现插拔检测。 //true:InputEvent;fasle:UEvent模式 mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
// Native callback. private void notifySwitch(long whenNanos, int switchValues, int switchMask) { if (DEBUG) { Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues) + ", mask=" + Integer.toHexString(switchMask)); } if ((switchMask & SW_LID_BIT) != 0) { final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0); mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen); } if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) { //此时回调到WiredAccessoryManager.java中 mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask); } }
接着就到了WiredAccessoryManager.java中处理,
// FROM: kernal --> InputManagerService --> WiredAccessoryManager.java ---> ,,, @Override public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) { if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos + " bits=" + switchCodeToString(switchValues, switchMask) + " mask=" + Integer.toHexString(switchMask)); //耳机种类 synchronized (mLock) { int headset; mSwitchValues = (mSwitchValues & ~switchMask) | switchValues; switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT)) { case 0: headset = 0; break; case SW_HEADPHONE_INSERT_BIT: headset = BIT_HEADSET_NO_MIC; break; case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break; case SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break; default: headset = 0; break; } updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC)) | headset); } }
大致方法流程就是:
notifyWiredAccessoryChanged ---> updateLocked ---> mHandler::handlerMessage -- > setDevicesState ---> ....AudioService.java ----> .... ---> AudioSystem
/** * Compare the existing headset state with the new state and pass along accordingly. Note * that this only supports a single headset at a time. Inserting both a usb and jacked headset * results in support for the last one plugged in. Similarly, unplugging either is seen as * unplugging all. * * @param newName One of the NAME_xxx variables defined above. * @param newState 0 or one of the BIT_xxx variables defined above. */ private void updateLocked(String newName, int newState) { // Retain only relevant bits int headsetState = newState & SUPPORTED_HEADSETS; int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); boolean h2wStateChange = true; boolean usbStateChange = true; //打印相关信息,耳机设备名称,最新状态,更新前状态 //这里可以查看log信息 if (LOG) Slog.v(TAG, "newName=" + newName + " newState=" + newState + " headsetState=" + headsetState + " prev headsetState=" + mHeadsetState); if (mHeadsetState == headsetState) { Log.e(TAG, "No state change."); return; } // reject all suspect transitions: only accept state changes from: // - a: 0 headset to 1 headset // - b: 1 headset to 0 headset if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC)) { Log.e(TAG, "Invalid combination, unsetting h2w flag"); h2wStateChange = false; } // - c: 0 usb headset to 1 usb headset // - d: 1 usb headset to 0 usb headset if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) { Log.e(TAG, "Invalid combination, unsetting usb flag"); usbStateChange = false; } if (!h2wStateChange && !usbStateChange) { Log.e(TAG, "invalid transition, returning ..."); return; } mWakeLock.acquire(); //发送消息处理,告知当前状态改变,继续做更新处理。 Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState, mHeadsetState, newName); mHandler.sendMessage(msg); //刷新保存当前最新状态 mHeadsetState = headsetState; }
private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_NEW_DEVICE_STATE: //分别对应:new_headSet_status,old_headSet_status,newName setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); mWakeLock.release(); } } }; private void setDevicesState( int headsetState, int prevHeadsetState, String headsetName) { synchronized (mLock) { int allHeadsets = SUPPORTED_HEADSETS; for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { if ((curHeadset & allHeadsets) != 0) { //没有看懂这里是干什么? 不过大致可以了解到,应该是判断该device是否支持,是则基础处理。 setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName); allHeadsets &= ~curHeadset; } } } } private void setDeviceStateLocked(int headset, int headsetState, int prevHeadsetState, String headsetName) { //状态改变了 if ((headsetState & headset) != (prevHeadsetState & headset)) { int device; int state; //当耳机插入和拔出,上报的event中,分别是1和0因此这里的state应该是决定当前是插入还是拔出耳机。 if ((headsetState & headset) != 0) { state = 1;//插入耳机 } else { state = 0;//拔出耳机 } if (headset == BIT_HEADSET) { device = AudioManager.DEVICE_OUT_WIRED_HEADSET; } else if (headset == BIT_HEADSET_NO_MIC){ device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; } else if (headset == BIT_USB_HEADSET_ANLG) { device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; } else if (headset == BIT_USB_HEADSET_DGTL) { device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; } else if (headset == BIT_HDMI_AUDIO) { device = AudioManager.DEVICE_OUT_AUX_DIGITAL; } else { Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); return; } if (LOG) Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected")); //BIT_HEADSET,1,h2w mAudioManager.setWiredDeviceConnectionState(device, state, headsetName); } }
/** * Indicate wired accessory connection state change. * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx) * @param state new connection state: 1 connected, 0 disconnected * @param name device name * {@hide} */ public void setWiredDeviceConnectionState(int device, int state, String name) { IAudioService service = getService(); try { //转到调用AudioService中的同名方法。 //BIT_HEADSET,1,h2w service.setWiredDeviceConnectionState(device, state, name); } catch (RemoteException e) { Log.e(TAG, "Dead object in setWiredDeviceConnectionState "+e); } }
//from: AudioManager::setWiredDeviceConnectionState public void setWiredDeviceConnectionState(int device, int state, String name) { synchronized (mConnectedDevices) { int delay = checkSendBecomingNoisyIntent(device, state); queueMsgUnderWakeLock(mAudioHandler, MSG_SET_WIRED_DEVICE_CONNECTION_STATE, device, state, name, delay); } }
case MSG_SET_WIRED_DEVICE_CONNECTION_STATE: //device,state ,name onSetWiredDeviceConnectionState(msg.arg1, msg.arg2, (String)msg.obj); mAudioEventWakeLock.release(); break;
//called from AudioHandler::handleMessage(Message msg) private void onSetWiredDeviceConnectionState(int device, int state, String name) { synchronized (mConnectedDevices) { // if ((state == 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) { setBluetoothA2dpOnInt(true); } boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0); //处理1 handleDeviceConnection((state == 1), device, (isUsb ? name : "")); if (state != 0) { //连接状态 if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)) { setBluetoothA2dpOnInt(false); } if ((device & mSafeMediaVolumeDevices) != 0) { //处理2 sendMsg(mAudioHandler, MSG_CHECK_MUSIC_ACTIVE, SENDMSG_REPLACE, 0, 0, null, MUSIC_ACTIVE_POLL_PERIOD_MS); } } if (!isUsb) { //处理3 sendDeviceConnectionIntent(device, state, name); } } }
在来看看注册WiredAccessoryManager的代码。in SystemServer.java
创建InputManagerService 实例对象并且注册,
Slog.i(TAG, "Input Manager"); inputManager = new InputManagerService(context);
if (!disableMedia) { try { Slog.i(TAG, "Wired Accessory Manager"); // Listen for wired headset changes inputManager.setWiredAccessoryCallbacks( new WiredAccessoryManager(context, inputManager)); } catch (Throwable e) { reportWtf("starting WiredAccessoryManager", e); } }
InputManagerService.java ; WiredAccessoryManager.java ; WiredAccessoryCallbacks;SystemServer 之间的关系可以使用下面的图大致标注出来。
因为不是很少画,所以这个图能看就行。
有几个疑问:
耳机插入显示对应图标
耳机插入的流程大概就是上面所描述。当耳机插入后会有广播发出,在AudioService.java的
sendDeviceConnectionIntent方法中
private void sendDeviceConnectionIntent(int device, int state, String name) { Intent intent = new Intent(); intent.putExtra("state", state); intent.putExtra("name", name); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); int connType = 0; if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { connType = AudioRoutesInfo.MAIN_HEADSET; intent.setAction(Intent.ACTION_HEADSET_PLUG); intent.putExtra("microphone", 1); } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) { connType = AudioRoutesInfo.MAIN_HEADPHONES; intent.setAction(Intent.ACTION_HEADSET_PLUG); intent.putExtra("microphone", 0); } else if (device == AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET) { connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS; intent.setAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG); } else if (device == AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET) { connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS; intent.setAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG); } else if (device == AudioSystem.DEVICE_OUT_AUX_DIGITAL) { connType = AudioRoutesInfo.MAIN_HDMI; intent.setAction(Intent.ACTION_HDMI_AUDIO_PLUG); } //下面这段 synchronized (mCurAudioRoutes) { if (connType != 0) { int newConn = mCurAudioRoutes.mMainType; if (state != 0) { newConn |= connType; } else { newConn &= ~connType; } if (newConn != mCurAudioRoutes.mMainType) { mCurAudioRoutes.mMainType = newConn; sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP, 0, 0, null, 0); } } } final long ident = Binder.clearCallingIdentity(); try { //发送广播ACTION:ACTION_HEADSET_PLUG,上层应用Settings应用中就可以接收该广播,以更新耳机图标。 ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); } finally { Binder.restoreCallingIdentity(ident); } }
ACTION_HEADSET_PLUG的广播。
因此耳机的显示在应用层的SystemUI里面处理。
后面我就不重新写了,可以参考这一篇博客:http://www.it165.net/pro/html/201404/12621.html
当默认为InputEvent处理插拔时,是如何从Kernal转到InputManagerService的?