Android 耳机事件传递流程


本文主要通过项目中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的内容,放在下一章讲解。