本文主要通过项目中Bug,讲解InputManager的事件传递过程
在项目中,遇到问题:插入不带麦耳机,状态栏显示仍然是带麦图标。 解决此问题涉及到耳机的拔插事件传递流程,在此分析一下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.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 /framework/base/media/java/android/media/AudioManager.java
AudioService.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>
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());
}
useDevInputEventForAudioJack设置为true使用/dev/input/event,FALSE时设置为UEvent. 在这里我们可以看到,当前使用的是UEvent方式。(UEvent方式支持热插拔,是一种适合耳机拔插事件的方式。)
<font color=#000000 size=5> 2.2 IMS 耳机事件传递</font>
插入耳机后,驱动层会将耳机事件首先,传递到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);
}
......
......
}
@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();
}
这个回调的初始化在setWiredAccessoryCallbacks()函数实现
public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) {
mWiredAccessoryCallbacks = callbacks;
}
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);
可以看见,这里将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);
}
}
最终会跳到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;
}
@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;
}
}
};
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;
}
}
}
}
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);
}
}
}
@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();
}
}
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);
}
}
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);
}
@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;
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;
}
}
}
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);
}
}
}
上面有两个重要的函数,分别是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;
}
然后看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 ...
}
上面一通分析可能比较混乱,现在梳理一下整个函数调用与信息传递过程。
IMS.notifySwitch()—>
WiredAccessoryManager.updateLocked() —>
WiredAccessoryManager.setDevicesState() —>
WiredAccessoryManager.setDeviceStateLocked()—>
AudioManager.setWiredDeviceConnectionState()—>
AudioService.setWiredDeviceConnectionState()—>
AudioService.onSetWiredDeviceConnectionState()—>
handleDeviceConnection; sendDeviceConnectionIntent
可以看出来,最终所有的操作都是在AudioSerice这个大管家里面执行的,其中,handleDeviceConnection是用来更新系统的耳机连接状态的,sendDeviceConnectionIntent是用来像整个系统发送耳机状态信息的,包括是否带麦,能否连接等等。
到此为止,我们已经将耳机拔插事件的传递流程全部理清,里面还有部分内容需要深入探讨,这里不做分析,关于状态栏耳机图标显示错误的问题,主要涉及的是SystemUi的内容,放在下一章讲解。