Android4.×耳机插拔检测

Android4.2耳机插拔检测实现方法 


1. 耳机检测的硬件原理

一般的耳机检测包含普通的耳机检测和带mic的耳机检测两种,这两种耳机统称为Headset,而对于不带mic的耳机,一般称之为Headphone

对于Headset装置的插入检测,一般通过Jack即耳机插座来完成,大致的原理是使用带检测机械结构的耳机插座,将检测脚连到可GPIO中断上,当耳机插入时,耳机插头的金属会碰到检测脚,使得检测脚的电平产生变化,从而引起中断。这样就可以在中断处理函数中读取GPIO的的值,进一步判断出耳机是插入还是拔出。

而对于Headset是否带mic的检测,需要通过codec附加的micbias电流的功能。

Android耳机插拔可以有两个机制实现:

1. InputEvent

2. UEvent

其中UEventAndroid系统默认的耳机插拔机制,所以这里代码是基于UEvent实现的,对于InputEvent机制只是大概看了看,并没有具体实现。

1.1 两种机制的切换

Android默认提供了两种解决方法,那么一定也提供了两种方式的切换,这个提供切换的设置名为config_useDevInputEventForAudioJack( 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),对Android源代码进行全局搜索,可以看到它在frameworks/base/core/res/res/values/config.xml中,默认为false,即不使用InputEvent方式,另外源码的厂商相关的文件夹中也找到了相关的设置,如下:

/android/4.2/device/asus/flo/overlay/frameworks/base/core/res/res/values/config.xml

false

/android/4.2/device/samsung/manta/overlay/frameworks/base/core/res/res/values/config.xml

True

可以看到有些厂商的确是使用了InputEvent的方式来进行耳机检测。具体对这个变量的修改是在device下还是frameworks下我想应该都可以,device下可能更好。violet源码device/mstar/mstarnike/overlay/frameworks/base/core/res/res/values/config.xml 中,没有对config_useDevInputEventForAudioJack 设置。

1.2 Android耳机插拨检测流程

Android4.×耳机插拔检测

2 InputEvent

2.1 Framework层对InputEvent的处理机制

InputEvent的处理主要在frameworks/base/services/java/com/android/server/input/

InputManagerService.java中。在InputManagerService构造函数中,通过如下函数,

mUseDevInputEventForAudioJack = context.getResources().

                          getBoolean(R.bool.config_useDevInputEventForAudioJack);

判断当前是否通过InputEvent实现耳机插拔检测。

Android得到InputEvent后,会调用InputManagerService.javanotifySwitch的函数,进而转至WiredAccessoryManager.java文件中的notifyWiredAccessoryChanged函数,之后的流程就和UEvent相同了,在后续会讲到。

2.2 Kernel层的处理机制

Kernel层对耳机插拔InputEvent处理主要是通过input_report_key/input_report_switchinclude/linux/input.h来实现,而在实际使用中,ASOC已经为我们封装好了相应Jack接口函数,只要符合规范就可以拿来使用。下面列出几个常用的接口函数/sound/soc/soc-jack.c

int snd_soc_jack_new(structsnd_soc_codec *codec, 

              const char *id, int type, struct snd_soc_jack *jack)

生成一个新的jack对象,定义其被检测的类型,即可能插入的设备类型。一般定义为SND_JACK_HEADSET,其余也可以根据接口支持种类添加SND_JACK_LINEOUTSND_JACK_AVOUT等。

这个函数中调用了snd_jack_new,而在snd_jack_new中可以看到调用 input_allocate_device()分配了input device,就可以在后续产生input event了。

int snd_soc_jack_add_pins(structsnd_soc_jack *jack, int count, struct snd_soc_jack_pin *pins)

将之前定义好的pins加入dapm widgets中,方便dapm统一管理。这一步和InputEvent没有一定联系,可以不调用,主要是可以将耳机插座定义为widgets加入dapm进行省电管理。

viod snd_soc_jack_report(structsnd_soc_jack *jack, int status, int mask)

汇报jack插拔状态,主要完成以下两个工作:

 a) 根据插入拔出状态更新前面通过snd_soc_jack_add_pins加入的dapm pin的状态,对其进行上电下电管理。

b) 调用snd_jack_report,在其中通过input_report_key/input_report_switch来向上层汇报input event

基于上面的函数,可以用以下做法来实现基于InputEvent机制的耳机插拔检测:

a) snd_soc_jack_new 创建jack对象

b) snd_soc_jack_add_pins将其加入到dapm wigets

c) 通过request irq申请耳机插拔中断,在中断处理函数中通过检测线高低电平判断耳机是插入还是拔出,通过读取codec寄存器来判断是headset还是headphone

d) 根据判断结果调用snd_soc_jack_report发送InputEvent

此外,ASOC还提供了一个封装好的函数来实现上述c)d)步骤的功能:

int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, 

                        struct snd_soc_jack_gpio *gpios)

该函数通过标准GPIO驱动申请GPIOGPIO对应中断,并提供了统一的中断处理函数来汇报事件。此函数只适用于耳机中断接至GPIOGPIO驱动为Linux标准驱动的情况下,并且不支持mic检测。

3. UEvent

3.1 Switch 基本原理 

SwitchAndroid引进的新的驱动,目的是用于检测一些开关量,比如检测耳机插入、检测 USB 设备插入等。Switchsysfs文件系统中创建相应entry,用户可以通过sysfs与之交互; 此外还可以通过uevent机制与之交互, 从而检测switch状态。  

3.1.1 Switch的实现 

Switch classAndroid中实现为一个module,可动态加载;而具体的switch gpio则是基于 platform device框架。代码在drivers\switch\switch_class.cdrivers\switch\switch_gpio.c中。其中switch_class.c实现了一个switch class,而switch_gpio.c则是这个class中的一个device,即针对gpio的一个switch设备。  

switch_class.c文件创建了一个switch_class,实现了内核的switch机制,提供支持函数供其他switch device驱动调用。 

static int __init switch_class_init(void){       

return create_switch_class(); }  

static void __exit switch_class_exit(void){

       class_destroy(switch_class); }  

module_init(switch_class_init); 

module_exit(switch_class_exit);

init函数调用create_switch_class->class_create创建switch_class设备类。相对应 exit则是销毁这个设备类。  

该文件导出两个函数供其他switch设备驱动调用,分别是注册switch设备switch_dev_register和注销switch_dev_unregisterdrivers/switch/switch_class.c

int switch_dev_register(struct switch_dev *sdev)

{

        int ret;

        if (!switch_class) {

                ret = create_switch_class();

                if (ret < 0)

                        return ret;

        }

        sdev->index = atomic_inc_return(&device_count);

        sdev->dev = device_create(switch_class, NULL,

                MKDEV(0, sdev->index), NULL, sdev->name);

        if (IS_ERR(sdev->dev))

                return PTR_ERR(sdev->dev);

 

        ret = device_create_file(sdev->dev, &dev_attr_state);

        if (ret < 0)

                goto err_create_file_1;

        ret = device_create_file(sdev->dev, &dev_attr_name);

        if (ret < 0)

                goto err_create_file_2;

 

        dev_set_drvdata(sdev->dev, sdev);

        sdev->state = 0;

        return 0;

err_create_file_2:

        device_remove_file(sdev->dev, &dev_attr_state);

err_create_file_1:

        device_destroy(switch_class, MKDEV(0, sdev->index));

        printk(KERN_ERR "switch: Failed to register driver %s\n", sdev->name);

 

        return ret;

}

EXPORT_SYMBOL_GPL(switch_dev_register);

 

void switch_dev_unregister(struct switch_dev *sdev)

{

        device_remove_file(sdev->dev, &dev_attr_name);

        device_remove_file(sdev->dev, &dev_attr_state);

        dev_set_drvdata(sdev->dev, NULL);

        device_destroy(switch_class, MKDEV(0, sdev->index));

}

EXPORT_SYMBOL_GPL(switch_dev_unregister);

然后是两个sysfs操作函数(state_showname_show),分别用于输出switch devicenamestate。当用户读取sysfs中对应的switch entry/sys/class/switch/<dev_name>/name/sys/class/switch/<dev_name>/state)时候,系统会自动调用这两个函数向用户返回switch设备的名称和状态。 

static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf)

{

        struct switch_dev *sdev = (struct switch_dev *)dev_get_drvdata(dev);

        if (sdev->print_state) {

                int ret = sdev->print_state(sdev, buf);

                if (ret >= 0)

                        return ret;

        }

        return sprintf(buf, "%d\n", sdev->state);

}

 

static ssize_t name_show(struct device *dev, struct device_attribute *attr,

                char *buf)

{

        struct switch_dev *sdev = (struct switch_dev *)dev_get_drvdata(dev);

        if (sdev->print_name) {

                int ret = sdev->print_name(sdev, buf);

                if (ret >= 0)

                        return ret;

        }

        return sprintf(buf, "%s\n", sdev->name);

}

可见,这两个函数就是直接调用对应的switch_dev中的print_stateprint_name函数;如果没有定义这两个函数,则调用sprintf把信息打印到buf缓冲区里。  

最后是 switch_set_state 函数,该函数是内核内部使用,并不为用户调用,它完成的功能主要是两件事: 调用name_showstate_show输出switch设备名称和状态至sysfs文件系统发送uevent通知用户switch device的信息(名称和状态)  

switch_gpio.c文件基于switch class实现了一个gpioswitch设备驱动,其实现的原理如下:  基于platform device/driver框架,在probe函数中完成初始化,包括获取gpio的使用权限,设置gpio方向为输入,注册switch_dev设备,为gpio分配中断,指定中断服务程序,初始化一个gpio_switch_work工作,最后读取gpio初始状态。  

 GPIO 引脚状态发生变化时,则会触发中断,在中断服务程序中调用schedule_work,这个被schedulework即前面初始化的gpio_switch_work,最后这个work被执行,在gpio_switch_work函数中读取当前gpio电平,调用 switch_set_state更新sysfs并通过 uevent通知上层应用。  

这个设备驱动只实现了print_state函数:switch_gpio_print_state,没有实现 print_name函数。当gpio_switch_work执行的时候,里面调用switch_set_state->switch_gpio_print_state输出GPIO状态到 sysfs

3.1.2 Switch模块的用户接口 

sysfs文件为sys/class/switch/<dev_name>/name sys/class/switch/<dev_name>/stateuevent环境变量为SWITCH_NAME=<name> SWITCH_STATE=<state>sysfs文件系统和uevent机制。

UEvent机制比较简单,它基于switch driverswitch driver会在Android建立耳机插拔的目录/sys/devices/virtual/switch/h2w,在此目录下有个设备结点名为statedriver通过更新state的值,从而通知Android上层耳机状态的改变。

3.2 Framework层对UEvent的处理机制

Androidframeworks/base/services/java/com/android/server/WiredAccessoryManager. java中实现针对UEvent机制。

在这个文件中,从UEventObserver中继承了类WiredAccessoryObserver,在makeObservedUEventList中将要观察的事件加入到UEvent系统中:

if(!mUseDevInputEventForAudioJack) 

{

uei = new UEventInfo(NAME_H2W,BIT_HEADSET, BIT_HEADSET_NO_MIC);

……

}

可以看到,只有当不使用InputEvent时才添加UEvent事件,NAME_H2W就是headphone对应的switch driver的名字。BIT_HEADSETBIT_HEADSET_NO_MICstate结点的两个值,分别表示有mic和无mic的耳机。

UEvent事件到来时,类WiredAccessoryObserver中重载的onUEvent函数会被回调,从而调用updateStateLocked(devPath,name, state) ,其中state的值就是通过/sys/devices/virtual/switch/h2w/state结点来获得。

最后,程序会进入setDeviceStateLocked函数中处理,在setDeviceStateLocked中根据state的值设置device,然后调用mAudioManager.setWiredDeviceConnectionState,最后进入AudioPolicyManagerBase::setDeviceConnectionState

3.3 Kernel层的机制

前面说过,基于UEvent的耳机检测机制需要实现一只switch driver,它会建立一个用于耳机插拔检测的目录/sys/devices/virtual/switch/h2w,在此目录下有个设备结点名为stateswitch driver通过更新state的值,从而通知Android上层耳机状态的改变。

switch driver的目录在Linux kerneldrivers/staging/android/switch目录下,可以从目录名称中看到这只driver是为了Android专门产生的。在switch目录下有两个已有文件,switch_class.cswitch driver的内部实现,它提供了switch driver所需的一些APIswitch_gpio.c是一个例子,它实现了一个基于GPIO中断的switch driver

另外,在drivers/switch目录下也有同样的文件,不同之处是两者在Android下生成的结点的位置不同,如果要按照drivers/switch目录下的switch driver来实现,需要更改WiredAccessoryManager.java文件。

下面讲讲如何添加switch driver。添加switch driver很简单,可以仿照switch_gpio.c,大致步骤如下:

 a) drivers/staging/android/switch目录下新建一个platform driver,其中包含一个全局变量struct switch_dev sdev,即要注册的switch device

b) platformdriverprobe函数中调用switch_dev_register将前面的sdev注册到系统中。

c) 申请用于耳机检测的中断处理函数。对于耳机插拔来说,由于用户的插拔快慢等可能产生多次中断,所以一般是在中断处理函数中实现一个延时工作队列,即INIT_DELAYED_WORK,在队列的回调函数中来进行实际判断。

d) 当中断发生后,通过switch_set_state设置state节点的值,这个值要和WiredAccessoryManager.java文件中定义的一致,可参看BIT_HEADSETBIT_HEADSET_NO_MIC的定义。目前是0表示无耳机插入,1表示带Mic的耳机,2表示不带Mic的耳机。switch_set_state这个函数调用了kobject_uevent_env/kobject_uevent,这两个函数就是kernel通过uevent来通知user space的核心函数了。

3.4 基于UEvent耳机插入检测流程

    System ServerAndroid系统的核心,他在Dalvik虚拟机启动后立即开始初始化和运行。其它的系统服务在System Server进程的环境中运行。

    main函数中,首先检查系统时间设置和SamplingProfiler。然后加载一个叫android_servers的本地库,他提供本地方法的接口(源程序在framework/base/services/jni/目录中)。然后调用本地方法设置服务。然后执行一个死循环线程,该线程中启动了很多服务。

   public static void main(String[] args) {

        ... ...

        Environment.setUserRequired(true);

        System.loadLibrary("android_servers");

 

        Slog.i(TAG, "Entered the Android system server!");

        // Initialize native services.

        nativeInit();

 

        // This used to be its own separate thread, but now it is

        // just the loop we run on the main thread.

        ServerThread thr = new ServerThread();

        thr.initAndLoop();

    }    

}

ServerThread中启动了监听有线耳机接入的服务。

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

                }

            }

    base/services/java/com/android/server/WiredAccessoryManager.java

WiredAccessoryManager中使用了两种方式监听耳机的状态

在构造函数中获得mUseDevInputEventForAudioJack的状态,配置为false

public WiredAccessoryManager(Context context, InputManagerService inputManager) {

  PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

  mWakeLock = pm.newWakeLock(

              PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager");

        mWakeLock.setReferenceCounted(false);

        mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

        mInputManager = inputManager;

        mContext= context;  

        mUseDevInputEventForAudioJack =

           context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);

 

        mObserver = new WiredAccessoryObserver();

 

        context.registerReceiver(new BroadcastReceiver() {

                    @Override

                    public void onReceive(Context ctx, Intent intent) {

                        bootCompleted();

                    }   

                },  

                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);

    }   

在启动完成后就开启监听,注册了开机广播,开机后,会开始所有相关的UEvent,并且开始监听。private void bootCompleted()

private void bootCompleted() {

if (mUseDevInputEventForAudioJack) {

int switchValues = 0;

if (mInputManager.getSwitchState(-1, 

        InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) {

switchValues |= SW_HEADPHONE_INSERT_BIT;

            }

if (mInputManager.getSwitchState(-1, 

           InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) {

switchValues |= SW_MICROPHONE_INSERT_BIT;

            }

notifyWiredAccessoryChanged(0, switchValues,

                    SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT);

}

mObserver.init();

}       

WiredAccessoryManager中实例化了一个WiredAccessoryObserver,其就是通过UEvent方式来检测耳机的插入拔出状态,

 mObserver = new WiredAccessoryObserver();

 

class WiredAccessoryObserver extends UEventObserver {

private final List<UEventInfo> mUEventInfo;

 

public WiredAccessoryObserver() {

mUEventInfo = makeObservedUEventList();

}

WiredAccessoryObserver中,

private List<UEventInfo> makeObservedUEventList() {

List<UEventInfo> retVal = new ArrayList<UEventInfo>();

UEventInfo uei;

 

// Monitor h2w

if (!mUseDevInputEventForAudioJack) {

uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC);

if (uei.checkSwitchExists()) {

retVal.add(uei);

} else {

Slog.w(TAG, "This kernel does not have wired headset support");

 }

}

... ...

            return retVal;

}

WiredAccessoryObserver init

        void init() {

            synchronized (mLock) {

                if (LOG) Slog.v(TAG, "init()");

                char[] buffer = new char[1024];

 

                for (int i = 0; i < mUEventInfo.size(); ++i) {

                    UEventInfo uei = mUEventInfo.get(i);

                    try {

                        int curState;

                        FileReader file = new FileReader(uei.getSwitchStatePath());

                        int len = file.read(buffer, 0, 1024);

                        file.close();

                        curState = Integer.valueOf((new String(buffer, 0, len)).trim());

 

                        if (curState > 0) {

                            updateStateLocked(uei.getDevPath(), uei.getDevName(), curState);

                        }

                    } catch (FileNotFoundException e) {

                        Slog.w(TAG, uei.getSwitchStatePath() +

                                " not found while attempting to determine initial switch state");

                    } catch (Exception e) {

                        Slog.e(TAG, "" , e);

                    }

                }

            }

            // At any given time accessories could be inserted

            // one on the board, one on the dock and one on HDMI:

            // observe three UEVENTs

            for (int i = 0; i < mUEventInfo.size(); ++i) {

                UEventInfo uei = mUEventInfo.get(i);

                startObserving("DEVPATH="+uei.getDevPath());

            }

        }

通过startObserving("DEVPATH="+uei.getDevPath()); 来进行监听

startObserving("DEVPATH="+uei.getDevPath());

监听的节点是

shell@hammerhead:/ $ ls sys/class/switch/ -l                                   

lrwxrwxrwx root     root              2014-05-06 09:44 h2w -> ../../devices/virtual/switch/h2w

onUEvent时间到来的时候更新state

        public void onUEvent(UEventObserver.UEvent event) {

            if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());

 

            try {

                String devPath = event.get("DEVPATH");

                String name = event.get("SWITCH_NAME");

                int state = Integer.parseInt(event.get("SWITCH_STATE"));

                synchronized (mLock) {

                    updateStateLocked(devPath, name, state);

                }

            } catch (NumberFormatException e) {

                Slog.e(TAG, "Could not parse switch state from event " + event);

            }

        }

updateStateLocked(devPath, name, state) 

->   updateLocked(String newName, int newState)  

->   setDevicesState(int headsetState, int prevHeadsetState, String headsetName)  

->   setDeviceStateLocked()

 ->  mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);setDeviceStateLocked中会更新device的状态,并最终调用mAudioManager.setWiredDeviceConnectionState

private void setDeviceStateLocked(int headset, int headsetState, int prevHeadsetState, String headsetName)

 {

if ((headsetState & headset) != (prevHeadsetState & headset)) {

int device;

int 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"));

if(headsetName.equals("hdmi")&&state==1){

Intent intent=new Intent("android.intent.action.HDMI_PLUG");

intent.putExtra("state", 1);

intent.putExtra("name", "hdmi");

mContext.sendBroadcast(intent);

mHdmiWakeLock.acquire();

Log.d(TAG,"--- hdmi connect ");

}else if(headsetName.equals("hdmi")&&state==0){

Log.d(TAG,"--- hdmi disconnect ");

Intent intent=new Intent("android.intent.action.HDMI_PLUG");

intent.putExtra("state", 0);

intent.putExtra("name", "hdmi");

mContext.sendBroadcast(intent);

mHdmiWakeLock.release();

}

mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);

}

}

AudioManagersetWiredDeviceConnectionState实际是调用AudioServicesetWiredDeviceConnectionState方法。

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

}

}

最终会发送到上层一个广播:

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 {

ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);

} finally {

Binder.restoreCallingIdentity(ident);

}

}

 

 

 

Android4.×耳机插拔检测,布布扣,bubuko.com

Android4.×耳机插拔检测

上一篇:.NET 软件下面win10自动启动配置


下一篇:c# 强制删除文件夹 DirectoryInfo