1. 程式人生 > >Android 耳機插拔處理流程

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方式。

[email protected]:~/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裡面處理。

當預設為InputEvent處理插拔時,是如何從Kernal轉到InputManagerService的?

相關推薦

Android 耳機處理流程

最近遇到一個bug: 當播放音樂時,插拔耳機,此時鬧鐘響起,鬧鐘響鈴沒有聲音。 剛開始以為是鬧鐘問題,浪費了不少時間,後來發現,插拔耳機後,音樂播放也會出現沒有聲音。 使用命令, adb shell getevent 發現,插拔耳機時,底層事件有正常上報。 adb shel

android耳機的監聽

必須動態註冊,否則無效監聽 一、主要監聽事件 1.監聽有線耳機 Intent.ACTION_HEADSET_PLUG android.intent.action.HEADSET_PLUG 2,監聽藍芽耳機 BluetoothHeadset.ACTIO

android 耳機檢測(kernel)

Android的耳機檢測其實程式碼改動很少的,也是因為少吧,所以一直沒寫文件。就這麼拖了將近兩個月。  驅動程式有三個實現版本: 其一是 :drivers/char/micco_hsdetect.c 它通過 kobject_uevent 上報狀態給使用者空間。 其二是

Android事件處理流程--Vold

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android底層開發之耳機與音訊通道切換例項

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android事件處理詳解

        整個過程從Kernel檢測到SD卡插入事件開始,之前的一些硬體中斷的觸發以及driver的載入這裡並不敘述,一直到SD卡掛載訊息更新到“Android——系統設定——儲存”一項中。        1.    Kernel發出SD卡插入uevent。        2.    Netlink

高通平臺耳機檢測

高通耳機的插拔檢測需要配置NC或NO,並且使用匹配的耳機(歐標,美標)。 歐標,美標 市面的耳機有兩種標準,即歐標(也叫國標)和美標。高通低端晶片無法相容,只能支援一種標準,具體是支援支援哪種標準是由耳機底座決定的。 歐標,美標的本質區別是地和mic的位置不同,如下圖。

藍芽耳機按鍵在Android側的處理流程

    目前大多數音訊視訊裝置採用紅外遙控器,由於距離、角度、障礙物等的影響,紅外遙控器的應用受到了很大限制。藍芽無線通訊技術可以實現傳統紅外遙控全部應用功能,而且客服了紅外遙控器的侷限性。藍芽音訊視訊遙控應用框架(Audio Video Remote Control P

android之通過USB流程來了解android UEvent

UEvent,全稱User Space Event,是kernel通知使用者空間的一種機制;在android中很多地方使用到了UEvent機制,如圖: 像HDMI,Battery,USB相關等;當我們需要接受底層的UEvent的時候,我們就需要註冊一個UEventOb

Android 輸入管理服務-輸入事件到達之後的處理流程

content 例如 enter 技術 fontsize tail 流程 ref ora 接上一篇博客“Android 輸入管理服務啟動過程的流程”。這兩天分析了Android 輸入管理服務接收到輸入事件之後的處理流程,詳細流程例如以下面兩圖所看到的:

Android 檢測 USB 事件

靜態註冊 : AndroidManifest.xml: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"

Android顯示vsync訊號的虛擬化和處理流程

android系統在4.4之後加入了黃油計劃,surfaceflinger對顯示的處理也變得複雜起來。由於添加了vsyn虛擬化機制,app將要顯示的內容和surfaceflinger對顯示內容的合成分成了兩個部分,而兩者開始的訊號都是從vsync發出的。這裡就涉及vsync訊

C# Winform下一個熱的MIS/MRP/ERP框架12(資料處理基類)

/// <summary> /// 資料庫連線基類 /// </summary> public class DBContext { /// <summary> /// 預設的加密方法Key,用於使用者

Android rtsp 流媒體音視訊幀的處理流程

轉自 http://blog.sina.com.cn/foreverlovelost 先把從收到rtp包到封裝成完整的一幀涉及的相關函式從上到下羅列一遍, 後續在忘記的情況下理清的時候可以作為線索,不用從頭去分析程式碼 (MyHandler.h)onMessageRecei

Android下新增新的自定義鍵值和按鍵處理流程

[cpp] view plain copy print? <span style="font-family:FangSong_GB2312;font-size:18px;">/*  * Copyright (C) 2010 The Android Open So

Android按鍵事件處理流程

 剛接觸Android開發的時候,對touch、key事件的處理總是一知半解,一會是Activity裡的方法,一會是各種View 中的,自己始終不清楚到底哪個在先哪個在後,總之對整個處理流程沒能很好的把握。每次寫這部分程式碼的時候都有些心虛, 因為我不是很清楚什麼時候、

Android模組化(三)——模組可單獨編譯執行

轉自: 下面主要來講一下單一模組的獨立編譯執行和插拔式的整合。 單一模組的獨立編譯執行   模組化的好處之一就是單一模組可以獨立的開發編譯執行安裝到使用者的手機上,這樣就方便了對某一模組的單獨開發除錯,單一模組生成的apk體積也小,編譯時間也快,開發效率會高很多

Android Gallery3D原始碼學習總結(三)——Cache快取及資料處理流程

第一,在應用程式中有三個執行緒存在:主執行緒(隨activity的宣告週期啟動銷燬)、feed初始化執行緒(進入程式時只執行一次,用於載入相簿初始資訊)、feed監聽執行緒(一直在跑,監聽相簿和相片的變更)。 第二,不考慮CacheService 啟動的主要流程歸納如下: 1

Android Activity的按鍵事件處理流程

       Android裡,Activity按鍵事件相關的分發/處理函式有如下幾個:        1) public boolean dispatchKeyEvent(KeyEvent event);       2)public boolean onKeyDown

Android 7.0 Power 按鍵處理流程

Android 7.0  Power 按鍵處理流程 Power按鍵的處理邏輯由PhoneWindowManager來完成,本文只關注PhoneWindowManager中與Power鍵相關的內容,其他系統按鍵的處理類似也是在PhoneWindowManager中處理的。理