Android 开发过程中,使用耳机控制拍照,控制音乐播放,控制打电话等,线控在到蓝牙控制···
耳机也在不断升级,耳机拔插的程序这一块也在不断完善。因此,在定制开发过程中,阅读这部分代码流程是必修的功课,至少首先要搞清楚程序走的线路流程。下面结合我在实际工作中遇到的 bug ,需求定制等做一个简单的总结。
抓取事件命令:
PS C:\Users\xxxx> adb shell getevent -l add device 1: /dev/input/event11 name: "comp" add device 2: /dev/input/event10 name: "accel" add device 3: /dev/input/event9 name: "gyro" add device 4: /dev/input/event0 name: "Power Button" add device 5: /dev/input/event5 name: "Video Bus" add device 6: /dev/input/event8 name: "baytrailaudio Intel MID Audio Jack" add device 7: /dev/input/event6 name: "gpio-lesskey" add device 8: /dev/input/event7 name: "dollar_cove_power_button" add device 9: /dev/input/event3 name: "jsa1212_als" add device 10: /dev/input/event2 name: "jsa1212_ps" add device 11: /dev/input/event1 name: "sx9500" add device 12: /dev/input/event4 name: "goodix_ts"
add device 6: /dev/input/event8 name: "baytrailaudio Intel MID Audio Jack"
下面我们开始get event8 详细信息,下面事件信息分别是拔出和插入耳机时事件信息。<code>SW_MICROPHONE_INSERT </code>带mic的耳机。
PS C:\Users\xxxx> adb shell getevent -l /dev/input/event8 EV_SW SW_HEADPHONE_INSERT 00000000 EV_SW SW_MICROPHONE_INSERT 00000000 EV_SYN SYN_REPORT 00000000 EV_SW SW_HEADPHONE_INSERT 00000001 EV_SW SW_MICROPHONE_INSERT 00000001 EV_SYN SYN_REPORT 00000000
从上面的输出数据中我们可以看到,插入耳机上报1,拔出是0。
另外,我们可以从get parameter命令看到当前信息(注:这个命令是特别方案才有,取决于芯片商)
PS C:\Users\xxxx> adb shell parameter status ... Last Applied [Pending] Configurations: ====================================== OutputDevice.Private.Selected: WiredSpeakers [<none>] IHF.SDRC: Enabled [<none>] InputDevice.Selected: HeadsetMic [<none>] OutputDevice.Selected: Multimedia.IHF.Headset [<none>] IHF.StereoEq: Enabled [<none>] Headset.Selected: Digital [<none>] Voip.Tuning: Default [<none>] Calibration: Default [<none>] LPE_Mixer: Default [<none>] Audio.voice: Default [<none>] ...
<code>InputDevice.Selected</code> ,<code>OutputDevice.Selected</code>等我们可以看到是使用耳机状态。拔掉耳机,我们看一下具体的parameter信息:
Last Applied [Pending] Configurations: ====================================== OutputDevice.Private.Selected: WiredSpeakers [<none>] IHF.SDRC: Enabled [<none>] InputDevice.Selected: VoiceRecgnition.FrontMic [<none>] OutputDevice.Selected: Multimedia.IHF [<none>] IHF.StereoEq: Enabled [<none>] Headset.Selected: Digital [<none>] Voip.Tuning: Default [<none>] Calibration: Default [<none>] LPE_Mixer: Default [<none>]
此时相关参数发生了变化:<code>InputDevice.Selected</code>,<code>OutputDevice.Selected</code> 等已经发生了变化。
涉及到的类文件
UEvent和InputEvent的选择
这两个的切换主要是通过设置属性来的。该属性开关位于config.xml中:
<!-- When true use the linux /dev/input/event subsystem to detect the switch changes on the headphone/microphone jack. When false use the older uevent framework. --> <bool name="config_useDevInputEventForAudioJack">false</bool>
从注释里面我们可以看到设置为true,选择/dev/input/event ,设置为false 选择uevent 来控制事件的上报。
在InputManagerService.java 构造方法中,config_useDevInputEventForAudioJack的值初始化mUseDevInputEventForAudioJack 决定采用哪种方式。
public InputManagerService(Context context) { this.mContext = context; this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper()); //config_useDevInputEventForAudioJack mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack=" + mUseDevInputEventForAudioJack); mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); LocalServices.addService(InputManagerInternal.class, new LocalService()); }
插入耳机底层kernal事件上报后(注:这段过程需要研究),转到<code>InputManagerService.java</code>中的<code>notifySwitch</code>方法中。
接下来我们先看<code>notifySwitch</code>方法:
// Native callback. private void notifySwitch(long whenNanos, int switchValues, int switchMask) { ... 因为当前我们只看耳机插拔的模式,因此,其他的先排除。 if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) { mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask); } }
接下来是走到<code>mWiredAccessoryCallbacks</code> 回调中。
有个疑问:<code>mWiredAccessoryCallbacks</code>是什么呢?*
先看看回调
接下来我们先顺藤摸瓜从这个callBack的声明初始化开始,找到目标。
<code>WiredAccessoryCallbacks </code>接口的声明,它在<code>WiredAccessoryManager.java</code>类的内部,接口有两个方法,一个是<code>notifyWiredAccessoryChanged</code>另外一个是<code>systemReady</code> 。
/** * Callback interface implemented by WiredAccessoryObserver. */ public interface WiredAccessoryCallbacks { public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask); public void systemReady(); }
再看何时将它初始化:
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) { mWindowManagerCallbacks = callbacks; }
<code>setWindowManagerCallbacks</code>的调用在<code>SystemServer.java</code>中,也就是<code>InputManagerService</code>被创建的时候。
接下来转到SystemServer.java中:
inputManager = new InputManagerService(context); wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL/* always false*/, !mFirstBoot/* always true*/,mOnlyCore/* always false*/); ServiceManager.addService(Context.WINDOW_SERVICE, wm); //register the inputManagerService to ServiceManager ServiceManager.addService(Context.INPUT_SERVICE, inputManager); ... 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); } }
从上面的代码可以看到<code>WiredAccessoryManager</code>对象直接被注册为callBack 因此,<code>mWiredAccessoryCallbacks.notifyWiredAccessoryChanged</code>直接将后面的任务交给了<code>WiredAccessoryManager</code>类。
WiredAccessoryManager.java
notifyWiredAccessoryChanged方法:
@Override public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) { synchronized (mLock) { int headset; mSwitchValues = (mSwitchValues & ~switchMask) | switchValues; switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) { case 0: headset = 0; break; case SW_HEADPHONE_INSERT_BIT: headset = BIT_HEADSET_NO_MIC; break; case SW_LINEOUT_INSERT_BIT: headset = BIT_LINEOUT; 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; } //上面的switch 语句是看哪种耳机,带mic否? updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset); } }
接着转到updateLocked方法,该方法作用是check当前模式是否变化。也就是是否发生耳机拔出了或者插入了(0->1;1->0的变化)。state无变化则不会继续处理。
疑问:这里的BIT_USB_HEADSET_ANLG和BIT_USB_HEADSET_DGTL是什么?
/** * 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 | BIT_LINEOUT); boolean h2wStateChange = true; boolean usbStateChange = true; //fulairy add: check the state changed or not mHeadsetState is old state and headsetState is new state 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 | BIT_LINEOUT)) { 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; } //flairy add : h2wStateChange and usbStateChange all not changed . if (!h2wStateChange && !usbStateChange) { Log.e(TAG, "invalid transition, returning ..."); return; } mWakeLock.acquire(); //flairy add : yeah ,you changed pls mHandler deal with it . Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE/*what*/, headsetState/*arg0*/, //headsetState 新状态 mHeadsetState/*arg1*/, newName/*obj*/);//mHeadsetState 旧的状态,newName name of the headset . mHandler.sendMessage(msg); mHeadsetState = headsetState;// update the status . }
接下来mHandler处理:
private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_NEW_DEVICE_STATE: setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); mWakeLock.release(); break; ... } } }; //这个方法挺有意思...就是没看明白 :( 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) { setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName); allHeadsets &= ~curHeadset; } } } } private void setDeviceStateLocked(int headset, int headsetState, int prevHeadsetState, String headsetName) { if ((headsetState & headset) != (prevHeadsetState & headset)) { int outDevice = 0; int inDevice = 0; int state; //important: state set start if ((headsetState & headset) != 0) { state = 1; } else { state = 0; } // important : state set end if (headset == BIT_HEADSET) { outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET; inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET; } else if (headset == BIT_HEADSET_NO_MIC){ outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE; } else if (headset == BIT_LINEOUT){ outDevice = AudioManager.DEVICE_OUT_LINE; } else if (headset == BIT_USB_HEADSET_ANLG) { outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET; } else if (headset == BIT_USB_HEADSET_DGTL) { outDevice = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET; } else if (headset == BIT_HDMI_AUDIO) { outDevice = AudioManager.DEVICE_OUT_HDMI; } else { Slog.e(TAG, "setDeviceState() invalid headset type: "+headset); return; } ... //update the out device and in device . if (outDevice != 0) { mAudioManager.setWiredDeviceConnectionState(outDevice, state, headsetName); } //这里我们仅仅看in device. if (inDevice != 0) { mAudioManager.setWiredDeviceConnectionState(inDevice, state, headsetName); } } }
接下来的流程就交给了<code>AudioManager</code>,从代码看实际上真正的处理这是AudioService
IAudioService service = getService(); ... service.setWiredDeviceConnectionState(device, state, name);
因此我们跳过AudioManager.java直接从AduioService.java看。
setWiredDeviceConnectionState--> queueMsgUnderWakeLock-->sendMsg--> ... 从源代码看到这里,你可以发现AudioService是个非常重要的角色,因此它里面的控制逻辑很多建议多多理解。
接下来我们直接从handler处理消息开始。因为我们只搞清楚headset处理的这条线。
... case MSG_SET_WIRED_DEVICE_CONNECTION_STATE: onSetWiredDeviceConnectionState(msg.arg1, msg.arg2, (String)msg.obj); mAudioEventWakeLock.release(); break; ... private void onSetWiredDeviceConnectionState(int device, int state, String name) { synchronized (mConnectedDevices) { //state==0 ===> the device is disconnected. ... //ignore BluetoothA2dp Device. ... handleDeviceConnection((state == 1)/*FULAIRY ADD :true if connected , false if disconnected */, device, (isUsb ? name : ""/* FuLaiRy add :that's why we get empty string when we use common headset.*/)); ... // other conditions we also ignore // FuLAIRY ADD :Send broadcast ... if (!isUsb && (device != AudioSystem.DEVICE_IN_WIRED_HEADSET)) { sendDeviceConnectionIntent(device, state, name); } } }
<code>onSetWiredDeviceConnectionState</code>方法中我们忽略其他一些情况的处理,看最主要的两个: 一个是<code>handleDeviceConnection</code>,另外一个是<code>sendDeviceConnectionIntent</code>。因此,下面我们主要看着两个方法。
private boolean handleDeviceConnection(boolean connected, int device, String params) { synchronized (mConnectedDevices) { //Fulairy: mConnectedDevices is a hashMap : //private final HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>(); // the if means that if key and values are all equal,indicate the same device has been connected . boolean isConnected = (mConnectedDevices.containsKey(device) && (params.isEmpty() || mConnectedDevices.get(device).equals(params))); if (isConnected && !connected) { //断开 AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, mConnectedDevices.get(device)); mConnectedDevices.remove(device); return true; } else if (!isConnected && connected) { //连接 //接下来的处理在JNI方法 AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, params); mConnectedDevices.put(new Integer(device), params); return true; } } return false; } ...
JNI部分放到后面补充。
sendDeviceConnectionIntent
从这个方法,可以看到intent耳机插拔的广播我们可以在app层通过监听<code>ACTION_HEADSET_PLUG</code>。 intent携带了,当前最新状态state ,name以及microphone(耳机是否带麦)。可以从自带音乐播放器看看。
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 || device == AudioSystem.DEVICE_OUT_LINE) { /*do apps care about line-out vs headphones?*/ connType = AudioRoutesInfo.MAIN_HEADPHONES; intent.setAction(Intent.ACTION_HEADSET_PLUG); intent.putExtra("microphone", 0); } ... ... 割舍了也很重要的一些其他逻辑处理。 try { ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); // 后面的流程就不继续跟了,这个是通用,单独分出一条线比较好,check里面是如何运作的。 } finally ... }
说到这里我们不得不提一下,插入与拔出耳机通知栏耳机图标的显示与消失。
在原生应用中,搜索<code>ACTION_HEADSET_PLUG</code>我们是搜索不到的。因为没有做处理。下面我们自己添加。
涉及到的文件
添加广播注册
//FuLairy add: HeadSet start filter.addAction(Intent.ACTION_HEADSET_PLUG) ; //FuLairy add: HeadSet start mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); // FuLaiRy add Headset start mService.setIcon(SLOT_HEADSET, R.drawable.stat_sys_tty_mode, 0, null); mService.setIconVisibility(SLOT_HEADSET, false); // FuLaiRy add Headset end
新增update方法
//FuLairy add: HeadSet start public final void updateHeadSet(intent){ final int state = intent.getIntExtra("state",0/*default*/) ; final String name = intent.getStringExtra("name",""/*this always be empty string*/) ; final int microPhone =intent.getIntExtra("microphone",0/*default*/) ; switch(state){ case 0: //disconnected String contentDescription = mContext.getString(R.string.headset_disconnected); mService.setIcon(SLOT_HEADSET, R.drawable.head_set_disconnected_icon, 0, contentDescription); mService.setIconVisibility(SLOT_HEADSET, false); break; case 1: //connected String contentDescription = mContext.getString(R.string.headset_connected); if(microPhone==1){ mService.setIcon(SLOT_HEADSET, R.drawable.head_set_connected_icon_microphone, 0, contentDescription); mService.setIconVisibility(SLOT_HEADSET, true); }else if(microPhone==0){ mService.setIcon(SLOT_HEADSET, R.drawable.head_set_connected_icon_nomicrophone, 0, contentDescription); mService.setIconVisibility(SLOT_HEADSET, true); } break; default: mService.setIconVisibility(SLOT_HEADSET, false); }}//FuLairy add: HeadSet end
core/res/res/values/config.xml中添加: <item><xliff:g id="id">headset</xliff:g></item>
大概就这个思路,由于条件限制了,不然可以完整提供一下patch。流程清楚后,修改就快的多。
文章转载:
Android 耳机插拔流程源码跟踪浅析