原文鏈接:https://blog.csdn.net/frakie_kwok/article/details/73729804
感謝作者分享,轉載僅供備忘。
在項目中,遇到問題:插入不帶麥耳機,狀態欄顯示仍然是帶麥圖標。
解決此問題涉及到耳機的拔插事件傳遞流程,在此分析一下Android系統,耳機拔插流程源碼分析。
Android系統的耳機目前可以實現拍照、暫停/播放、打電話等功能,這一切的基礎是耳機拔插成功,結合InputManagerService的相關知識,主要從framework層面分析耳機插拔事件的傳遞。
一、驅動層的事件上傳
使用adb 命令可以查看當前手機插入的耳機狀態,命令爲:adb shell cat /sys/class/switch/h2w/state
- 插入帶麥耳機
ubuntu@ubuntu:~/headset$ adb shell cat /sys/class/switch/h2w/state
11
- 插入不帶麥耳機
ubuntu@ubuntu:~/headset$ adb shell cat /sys/class/switch/h2w/state
9
通過這個命令,我們可以初步判斷,狀態欄耳機顯示異常問題是出現在驅動層還是上層。
二、framework層代碼分析
前文提到,耳機拔插主要涉及到InputManagerService這個系統重量級服務。關於IMS(inoutManagerService)的具體介紹,請參考此文章:http://blog.csdn.net/jinzhuojun/article/details/41909159 ,作者很詳盡的介紹了IPM的事件讀取和分發過程。 本文章只挑揀IPM中與耳機事件相關的內容分析。
Android系統的事件主要分爲兩類:
按鍵事件(KeyEvent)
由物理按鍵產生的事件。 對於嵌入式設備,通常不會保留太多的物理按鍵,手機一般有Home, Back, Menu, Volume Down, Volume Up,我們討論的耳機事件也歸類在此。觸摸事件(TouchEvent)
在手機屏幕上面的點擊、拖動事件,以及它們的組合產生的各種事件。
2.1 涉及到的類
InputManagerService.java
/framework/base/services/core/java/com/android/server/input/InputManagerService.javaWiredAccessoryManager.java
/framework/base/services/core/java/com/android/server/WiredAccessoryManager.javaconfig.xml
/framework/base/core/res/res/values/config.xmlSystemServer.java
/framework/base/services/java/com/android/server/SystemServer.javaAudioManager.java
/framework/base/media/java/android/media/AudioManager.javaAudioService.java
framework/base/media/java/android/media/AudioService.java
2.2 設置Event上傳方式
Android中有兩種Event上傳方式:InputEvent
(linux的 /dev/input/event subsystem),和UEvent
(framework下的比較老的event方式),兩種方式的切換是通過屬性配置實現的,在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>
- 1
- 2
- 3
public InputManagerService(Context context) {
this.mContext = context;
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
String doubleTouchGestureEnablePath = context.getResources().getString(
R.string.config_doubleTouchGestureEnableFile);
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
LocalServices.addService(InputManagerInternal.class, new LocalService());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
useDevInputEventForAudioJack設置爲true使用/dev/input/event,FALSE時設置爲UEvent.
在這裏我們可以看到,當前使用的是UEvent方式。(UEvent方式支持熱插拔,是一種適合耳機拔插事件的方式。)
2.2 IMS 耳機事件傳遞
插入耳機後,驅動層會將耳機事件首先,傳遞到IMS的notifySwitch()
函數,驅動層的檢測與向上傳遞在此分析。
@2.2.1 notifySwitch()
// 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));
}
.....
.....
//此處是最重要的內容,在這裏,會將耳機拔插時間傳遞給mWiredAccessoryCallbacks回調
if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
switchMask);
}
......
......
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
@2.2.2 mWiredAccessoryCallbacks 的創建
從上述代碼中,可以看出來,耳機事件被傳遞到了mWiredAccessoryCallbacks這個回調中。WiredAccessoryCallbacks這個回調就定義在IMS.java中,可以看到如下代碼:
/**
* Callback interface implemented by WiredAccessoryObserver.
*/
public interface WiredAccessoryCallbacks {
public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask);
public void systemReady();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這個回調的初始化在setWiredAccessoryCallbacks()函數實現
public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) {
mWiredAccessoryCallbacks = callbacks;
}
- 1
- 2
- 3
setWiredAccessoryCallbacks()函數是在IMS在初始化的時候調用的,也就是在SystemServer.java中被調用的。
IMS被歸類在其他服務中,啓動是在startOtherService()函數中,代碼如下:
traceBeginAndSlog("StartWiredAccessoryManager");
try {
// Listen for wired headset changes
inputManager.setWiredAccessoryCallbacks(
new WiredAccessoryManager(context, inputManager));
} catch (Throwable e) {
reportWtf("starting WiredAccessoryManager", e);
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
可以看見,這裏將WiredAccessoryManager的實例傳遞給了mWiredAccessoryCallbacks ,所以,最終是WiredAccessoryManager在處理notifyWiredAccessoryChanged()方法。
@2.2.3 WiredAccessoryManager—notifyWiredAccessoryChanged
@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 | SW_LINEOUT_INSERT_BIT)) {
case 0:
headset = 0;
break;
//不帶mic的耳機
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;
//帶mic的耳機
case SW_MICROPHONE_INSERT_BIT:
headset = BIT_HEADSET;
break;
default:
headset = 0;
break;
}
updateLocked(NAME_H2W,
(mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
最終會跳到updateLocked方法,這個方法是用來檢查當前模式是否發生了變化,也就是耳機是否拔出、插入了(0->1 1->0)。
@2.2.4 WiredAccessoryManager—updateLocked()
/**
* 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;
if (LOG) Slog.v(TAG, "newName=" + newName
+ " newState=" + newState
+ " headsetState=" + headsetState
+ " prev headsetState=" + mHeadsetState);
//add
mAudioManager.setNowPrevHeadsetState(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 | 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;
}
if (!h2wStateChange && !usbStateChange) {
Log.e(TAG, "invalid transition, returning ...");
return;
}
mWakeLock.acquire();
Log.i(TAG, "MSG_NEW_DEVICE_STATE");
Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
mHeadsetState, "");
mHandler.sendMessage(msg);
mHeadsetState = headsetState;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
@2.2.4 接下來交給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()
setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
mWakeLock.release();
break;
case MSG_SYSTEM_READY:
onSystemReady();
mWakeLock.release();
break;
}
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
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) {
//seDeviceStateLocked()
setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName);
allHeadsets &= ~curHeadset;
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
private void setDeviceStateLocked(int headset,
int headsetState, int prevHeadsetState, String headsetName) {
if ((headsetState & headset) != (prevHeadsetState & headset)) {
int outDevice = 0;
int inDevice = 0;
int state;
if ((headsetState & headset) != 0) {
state = 1;
} else {
state = 0;
}
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;
}
if (LOG) {
Slog.v(TAG, "headsetName: " + headsetName +
(state == 1 ? " connected" : " disconnected"));
}
if(prevHeadsetState == 2 && headsetState == 1 && state == 0) {
try {
Thread.sleep(136);
} catch (InterruptedException e) {
// Ingore
}
}
if (outDevice != 0) {
mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName);
}
if (inDevice != 0) {
//經過一系列判斷,最終交給AudioManager
mAudioManager.setWiredDeviceConnectionState(inDevice, state, "", headsetName);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
@2.2.4 AudioManager—> setWiredDeviceConnectionState()
/**
* 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 type, int state, String address, String name) {
IAudioService service = getService();
try {
service.setWiredDeviceConnectionState(type, state, address, name,
mApplicationContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
service指的是AudioService,直接到AudioService中查看
public void setWiredDeviceConnectionState(int type, int state, String address, String name,
String caller) {
synchronized (mConnectedDevices) {
if (DEBUG_DEVICES) {
Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
+ address + ")");
}
int delay = checkSendBecomingNoisyIntent(type, state);
//@ 111
queueMsgUnderWakeLock(mAudioHandler,
MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
0,
0,
new WiredDeviceConnectionState(type, state, address, name, caller),
delay);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
private void queueMsgUnderWakeLock(Handler handler, int msg,
int arg1, int arg2, Object obj, int delay) {
final long ident = Binder.clearCallingIdentity();
// Always acquire the wake lock as AudioService because it is released by the
// message handler.
mAudioEventWakeLock.acquire();
Binder.restoreCallingIdentity(ident);
//@ 222
sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
@Override
public void handleMessage(Message msg) {
......
......
case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
{ WiredDeviceConnectionState connectState =
(WiredDeviceConnectionState)msg.obj;
onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
connectState.mAddress, connectState.mName, connectState.mCaller);
mAudioEventWakeLock.release();
}
break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
private static void sendMsg(Handler handler, int msg,
int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
if (existingMsgPolicy == SENDMSG_REPLACE) {
handler.removeMessages(msg);
} else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
return;
}
synchronized (mLastDeviceConnectMsgTime) {
long time = SystemClock.uptimeMillis() + delay;
handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
if (msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
msg == MSG_SET_A2DP_SINK_CONNECTION_STATE) {
mLastDeviceConnectMsgTime = time;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
上面有兩個重要的函數,分別是handleDeviceConnection和sendDeviceConnectionIntent
首先看handleDeviceConnection,這個函數處理耳機拔插時間
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
然後看sendDeviceConnectionIntent,這個函數向上層發送了一個有序廣播,裏面攜帶了耳機各個信息:是否帶麥、是否連接等等,上層應用可以聽過接收ACTION_HEADSET_PLUG這個廣播進而解析耳機插入時攜帶的內容。
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 ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
上面一通分析可能比較混亂,現在梳理一下整個函數調用與信息傳遞過程。
IMS.notifySwitch()—>
WiredAccessoryManager.updateLocked() —>
WiredAccessoryManager.setDevicesState() —>
WiredAccessoryManager.setDeviceStateLocked()—>
AudioManager.setWiredDeviceConnectionState()—>
AudioService.setWiredDeviceConnectionState()—>
AudioService.onSetWiredDeviceConnectionState()—>
handleDeviceConnection; sendDeviceConnectionIntent
可以看出來,最終所有的操作都是在AudioSerice這個大管家裏面執行的,其中,handleDeviceConnection是用來更新系統的耳機連接狀態的,sendDeviceConnectionIntent是用來像整個系統發送耳機狀態信息的,包括是否帶麥,能否連接等等。
到此爲止,我們已經將耳機拔插事件的傳遞流程全部理清,裏面還有部分內容需要深入探討,這裏不做分析,關於狀態欄耳機圖標顯示錯誤的問題,主要涉及的是SystemUi的內容,放在下一章講解。