Android 耳机插拔处理流程

最近遇到一个bug: 当播放音乐时,插拔耳机,此时闹钟响起,闹钟响铃没有声音。

刚开始以为是闹钟问题,浪费了不少时间,后来发现,插拔耳机后,音乐播放也会出现没有声音。

使用命令,

adb shell getevent 发现,插拔耳机时,底层事件有正常上报。

adb shell  parameter status >  status.log  查看,log中,当耳机拔出后,还是HeadSet状态,此时扬声器模式是disabled的,所以没有声音,再次插入耳机就有声音了。log属于特定打印,不同机子不一样。

SelectedInputDevice = <none>

SelectedOutputDevice = Headset

因此,特地代码跟踪了一下耳机处理流程。 参考了几篇大牛们的博客。

代码版本主要以Android 4.4和Android 5.0的为主。

主要有两种处理方式、

  1. UEvent
  2. InputEvent

Android 系统默认是使用UEvent的方式。两种模式的切换可以使用配置项来完成。

在:android4.4/frameworks/base/core/res/res/values/config.xml 中有

config.xml:1151:    <bool name="config_useDevInputEventForAudioJack">false</bool>  配置项。当为false 则使用UEVent方式,为true则使用InputEvent方式,现在大多使用后者,具体得看厂商的定制代码。

比如我这里的device目录下,配置为true,使用的InputEvent方式。

xxx_rtfsc@xxx-HP-EliteDesk-880-G1-TWR:~/rtfsc/xxxxx_5.0/device$ grep -rn config_useDevInputEventForAudioJack
xxxx/common/overlays_aosp/frameworks/base/core/res/res/values/config.xml:40:    <bool name="config_useDevInputEventForAudioJack">true</bool>



一 InputEvent

这种方式主要InputManagerService.java(framework/base/services/java/com/android/server/input/InputManagerService.java)中处理。

在InputManagerService的构造函数中,获取config_useDevInputEventForAudioJack的值,判断当前是否是使用InputEvent模式。

        //判断当前是否使用InputEvent实现插拔检测。
        //true:InputEvent;fasle:UEvent模式
        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);

当底层事件上报后,会转到InputManagerService的 notifySwitch方法中处理,然后回调到 WiredAccessoryManager中,。


    // 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));
        }

        if ((switchMask & SW_LID_BIT) != 0) {
            final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0);
            mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
        }

        if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
            //此时回调到WiredAccessoryManager.java中
            mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
                    switchMask);
        }
    }

提问: notifyWiredAccessoryChanged传递的参数分别是?


接着就到了WiredAccessoryManager.java中处理,

// FROM: kernal --> InputManagerService --> WiredAccessoryManager.java ---> ,,,
    @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)) {
                case 0:
                    headset = 0;
                    break;

                case SW_HEADPHONE_INSERT_BIT:
                    headset = BIT_HEADSET_NO_MIC;
                    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;
            }

            updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC)) | headset);
        }
    }

后面的流程就和UEvent的差不多了。

大致方法流程就是:

notifyWiredAccessoryChanged ---> updateLocked  ---> mHandler::handlerMessage -- > setDevicesState ---> ....AudioService.java ----> .... ---> AudioSystem

 /**
     * 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);
        boolean h2wStateChange = true;
        boolean usbStateChange = true;
        
        //打印相关信息,耳机设备名称,最新状态,更新前状态
        //这里可以查看log信息
        if (LOG) Slog.v(TAG, "newName=" + newName
                + " newState=" + newState
                + " headsetState=" + headsetState
                + " prev 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)) {
            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();
        
        //发送消息处理,告知当前状态改变,继续做更新处理。
        Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
                mHeadsetState, newName);
        mHandler.sendMessage(msg);
        
        //刷新保存当前最新状态
        mHeadsetState = headsetState;
    }

 private final Handler mHandler = new Handler(Looper.myLooper(), null, true) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_NEW_DEVICE_STATE:
                    //分别对应:new_headSet_status,old_headSet_status,newName
                    setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
                    mWakeLock.release();
            }
        }
    };

    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) {
                    //没有看懂这里是干什么? 不过大致可以了解到,应该是判断该device是否支持,是则基础处理。
                    setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName);
                    allHeadsets &= ~curHeadset;
                }
            }
        }
    }

    private void setDeviceStateLocked(int headset,
            int headsetState, int prevHeadsetState, String headsetName) {
        //状态改变了
        if ((headsetState & headset) != (prevHeadsetState & headset)) {
            int device;
            int state;

            //当耳机插入和拔出,上报的event中,分别是1和0因此这里的state应该是决定当前是插入还是拔出耳机。
            if ((headsetState & headset) != 0) {
                state = 1;//插入耳机
            } else {
                state = 0;//拔出耳机
            }

            if (headset == BIT_HEADSET) {
                device = AudioManager.DEVICE_OUT_WIRED_HEADSET;
            } else if (headset == BIT_HEADSET_NO_MIC){
                device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
            } else if (headset == BIT_USB_HEADSET_ANLG) {
                device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
            } else if (headset == BIT_USB_HEADSET_DGTL) {
                device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
            } else if (headset == BIT_HDMI_AUDIO) {
                device = AudioManager.DEVICE_OUT_AUX_DIGITAL;
            } else {
                Slog.e(TAG, "setDeviceState() invalid headset type: "+headset);
                return;
            }

            if (LOG)
                Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected"));
            //BIT_HEADSET,1,h2w
            mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);
        }
    }


AudioManager.java
     /**
     * 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 device, int state, String name) {
        IAudioService service = getService();
        try {
            //转到调用AudioService中的同名方法。
            //BIT_HEADSET,1,h2w
            service.setWiredDeviceConnectionState(device, state, name);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in setWiredDeviceConnectionState "+e);
        }
    }

AudioService.java

    //from: AudioManager::setWiredDeviceConnectionState
    public void setWiredDeviceConnectionState(int device, int state, String name) {
        synchronized (mConnectedDevices) {
            int delay = checkSendBecomingNoisyIntent(device, state);
            queueMsgUnderWakeLock(mAudioHandler,
                    MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
                    device,
                    state,
                    name,
                    delay);
        }
    }

                case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
                    //device,state ,name
                    onSetWiredDeviceConnectionState(msg.arg1, msg.arg2, (String)msg.obj);
                    mAudioEventWakeLock.release();
                    break;

 //called from AudioHandler::handleMessage(Message msg)
    private void onSetWiredDeviceConnectionState(int device, int state, String name)
    {
        synchronized (mConnectedDevices) {
            //
            if ((state == 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
                    (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) {
                setBluetoothA2dpOnInt(true);
            }
            boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0);
            //处理1
            handleDeviceConnection((state == 1), device, (isUsb ? name : ""));

            if (state != 0) {
            //连接状态
                if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
                    (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)) {
                    setBluetoothA2dpOnInt(false);
                }
                if ((device & mSafeMediaVolumeDevices) != 0) {
                    //处理2
                    sendMsg(mAudioHandler,
                            MSG_CHECK_MUSIC_ACTIVE,
                            SENDMSG_REPLACE,
                            0,
                            0,
                            null,
                            MUSIC_ACTIVE_POLL_PERIOD_MS);
                }
            }
            if (!isUsb) {
            //处理3
                sendDeviceConnectionIntent(device, state, name);
            }
        }
    }

上面都是贴了一些对应代码,先看清楚走的流程,在来详细分析。


在来看看注册WiredAccessoryManager的代码。in   SystemServer.java

创建InputManagerService 实例对象并且注册,

            Slog.i(TAG, "Input Manager");
            inputManager = new InputManagerService(context);

            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);
                }
            }

这里inputManager设置了回调,因为WiredAccessoryManager 实现了InputManagerService的回调接口。

InputManagerService.java ; WiredAccessoryManager.java ; WiredAccessoryCallbacks;SystemServer 之间的关系可以使用下面的图大致标注出来。

因为不是很少画,所以这个图能看就行。





有几个疑问:

  • 插入耳机,状态栏会有耳机图标显示,处理流程?
  • 当默认为InputEvent处理插拔时,是如何从Kernal转到InputManagerService的?
  • 蓝牙耳机的相关处理流程? 相似


耳机插入显示对应图标

耳机插入的流程大概就是上面所描述。当耳机插入后会有广播发出,在AudioService.java的

sendDeviceConnectionIntent方法中

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) {
            connType = AudioRoutesInfo.MAIN_HEADPHONES;
            intent.setAction(Intent.ACTION_HEADSET_PLUG);
            intent.putExtra("microphone", 0);
        } else if (device == AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET) {
            connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
            intent.setAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG);
        } else if (device == AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET) {
            connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS;
            intent.setAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG);
        } else if (device == AudioSystem.DEVICE_OUT_AUX_DIGITAL) {
            connType = AudioRoutesInfo.MAIN_HDMI;
            intent.setAction(Intent.ACTION_HDMI_AUDIO_PLUG);
        }

        //下面这段
        synchronized (mCurAudioRoutes) {
            if (connType != 0) {
                int newConn = mCurAudioRoutes.mMainType;
                if (state != 0) {
                    newConn |= connType;
                } else {
                    newConn &= ~connType;
                }
                if (newConn != mCurAudioRoutes.mMainType) {
                    mCurAudioRoutes.mMainType = newConn;
                    sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
                            SENDMSG_NOOP, 0, 0, null, 0);
                }
            }
        }

        final long ident = Binder.clearCallingIdentity();
        try {
            //发送广播ACTION:ACTION_HEADSET_PLUG,上层应用Settings应用中就可以接收该广播,以更新耳机图标。
            ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

这里广播发出后,注册到ActiviryManagerService中,这样注册了对应广播的地方就能接收到该广播。我们需要看的是

ACTION_HEADSET_PLUG的广播。

因此耳机的显示在应用层的SystemUI里面处理。

后面我就不重新写了,可以参考这一篇博客:http://www.it165.net/pro/html/201404/12621.html


当默认为InputEvent处理插拔时,是如何从Kernal转到InputManagerService的?



你可能感兴趣的:(Android开发,frameworks)