Android耳机耳机,Android 耳机插拔流程源码跟踪浅析
Android 開發過程中,使用耳機控制拍照,控制音樂播放,控制打電話等,線控再到藍牙控... 耳機也在不斷升級,耳機插拔的程序這一塊也在不斷完善。因此,在定制開發過程中,閱讀這部分流程代碼是必修的功課了,至少首先的要搞清楚程序走的線路流程。下面結合我在實際工作過程中遇到的bug,需求定制等做個簡單的總結。
第一節,插拔耳機時,事件上報
抓取事件命令:
查看有哪些事件可以get
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"
event8正式我們想要查看的event。
add device 6: /dev/input/event8
name: "baytrailaudio Intel MID Audio Jack"
下面我們開始get event8 詳細信息,下面事件信息分別是拔出和插入耳機時事件信息。SW_MICROPHONE_INSERT 帶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 []
IHF.SDRC: Enabled []
InputDevice.Selected: HeadsetMic []
OutputDevice.Selected: Multimedia.IHF.Headset []
IHF.StereoEq: Enabled []
Headset.Selected: Digital []
Voip.Tuning: Default []
Calibration: Default []
LPE_Mixer: Default []
Audio.voice: Default []
...
InputDevice.Selected ,OutputDevice.Selected等我們可以看到是使用耳機狀態。拔掉耳機,我們看一下具體的parameter信息:
Last Applied [Pending] Configurations:
======================================
OutputDevice.Private.Selected: WiredSpeakers []
IHF.SDRC: Enabled []
InputDevice.Selected: VoiceRecgnition.FrontMic []
OutputDevice.Selected: Multimedia.IHF []
IHF.StereoEq: Enabled []
Headset.Selected: Digital []
Voip.Tuning: Default []
Calibration: Default []
LPE_Mixer: Default []
此時相關參數發生了變化:InputDevice.Selected,OutputDevice.Selected 等已經發生了變化。
第二節,插入拔出framework部分相關源碼流程分析
涉及到的類文件
InputManagerService.java
./framework/base/services/core/java/com/android/server/input/InputManagerService.java
*WiredAccessoryManager.java.
./framework/base/services/core/java/com/android/server/WiredAccessoryManager.java
config.xml
./framework/base/core/res/res/values/config.xml
SystemServer.java
./framework/base/services/java/com/android/server/SystemServer.java
AudioManager.java
./base/media/java/android/media/AudioManager.java
AudioService.java
./base/media/java/android/media/AudioService.java
UEvent和InputEvent的選擇
這兩個的切換主要是通過設置屬性來的。該屬性開關位于config.xml中:
false
從注釋里面我們可以看到設置為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事件上報后(注:這段過程需要研究),轉到InputManagerService.java中的notifySwitch方法中。
接下來我們先看notifySwitch方法:
// Native callback.
private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
... 因為當前我們只看耳機插拔的模式,因此,其他的先排除。
if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
switchMask);
}
}
接下來是走到mWiredAccessoryCallbacks 回調中。
有個疑問:mWiredAccessoryCallbacks是什么呢?*
先看看回調
接下來我們先順藤摸瓜從這個callBack的聲明初始化開始,找到目標。
WiredAccessoryCallbacks 接口的聲明,它在WiredAccessoryManager.java類的內部,接口有兩個方法,一個是notifyWiredAccessoryChanged另外一個是systemReady 。
/**
* 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;
}
setWindowManagerCallbacks的調用在SystemServer.java中,也就是InputManagerService被創建的時候。
接下來轉到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);
}
}
從上面的代碼可以看到WiredAccessoryManager對象直接被注冊為callBack 因此,mWiredAccessoryCallbacks.notifyWiredAccessoryChanged直接將后面的任務交給了WiredAccessoryManager類。
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);
}
}
}
接下來的流程就交給了AudioManager,從代碼看實際上真正的處理這是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);
}
}
}
onSetWiredDeviceConnectionState方法中我們忽略其他一些情況的處理,看最主要的兩個: 一個是handleDeviceConnection,另外一個是sendDeviceConnectionIntent。因此,下面我們主要看著兩個方法。
handleDeviceConnection
private boolean handleDeviceConnection(boolean connected, int device, String params) {
synchronized (mConnectedDevices) {
//Fulairy: mConnectedDevices is a hashMap :
//private final HashMap mConnectedDevices = new HashMap ();
// 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層通過監聽ACTION_HEADSET_PLUG。 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 ...
}
說到這里我們不得不提一下,插入與拔出耳機通知欄耳機圖標的顯示與消失。
第三節,插拔耳機,通知欄圖標的顯示與消失。
在原生應用中,搜索ACTION_HEADSET_PLUG我們是搜索不到的。因為沒有做處理。下面我們自己添加。
涉及到的文件
PhoneStatusBarPolicy.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone
添加廣播注冊
//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中添加:
headset
大概就這個思路,由于條件限制了,不然可以完整提供一下patch。流程清楚后,修改就快的多。
結尾:
親身實踐才是王道... 文章只是個拋磚引玉。
歡迎批評指正。
Thank you all .
Congratulations!!Happy new year!!!
總結
以上是生活随笔為你收集整理的Android耳机耳机,Android 耳机插拔流程源码跟踪浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Excel]vlookup的内在逻辑以
- 下一篇: android耳机上报流程,Androi