检测安卓设备缺少听筒(只有扬声器模式)

18

我有一个应用程序,当在手机上使用时会显示扬声器切换按钮。这个切换按钮可以在耳机和扬声器之间进行音频路由的切换。然而,当应用程序在平板电脑(或任何没有耳机的设备)上运行时,我希望删除这个切换按钮,因为所有的音频都通过扬声器进行路由。

理想情况下,我想使用一些类型的isEarpiecePresent()调用,或者可能检查某个配置对象上的标志来查找此信息,但我在API中找不到任何类似的东西。

我尝试通过调用AudioManager.setSpeakerphoneOn(false),然后检查AudioManager.isSpeakerphoneOn()来解决问题,希望它仍然返回true,这样我就可以依此进行键控。但系统返回了false,即使音频仍然通过扬声器进行路由。

目前,我正在考虑检查电话功能,尽管这并不完全符合我的需求。还有其他的想法吗?


1
仅供参考,我认为电话功能可能不太可靠,例如我所拥有的三星Galaxy Tab支持电话功能,但却没有听筒扬声器。http://en.wikipedia.org/wiki/Samsung_Galaxy_Tab - Dan J
1
你有检查过这个吗?http://developer.android.com/training/managing-audio/audio-output.html - Dusko
谢谢stetocina,但不幸的是这些文档中没有关于检测音频输出硬件的内容。 - Josh
还有一些Galaxy Tab设备不支持电话功能(没有蜂窝硬件),但是有一个听筒,据说是为了VoIP应用程序。 - Grishka
6个回答

5
所以丑陋的部分是 - 谷歌正在忽略这个问题。美好的部分是,我成功地检查了耳机,并且在5/5台设备上都可以正常工作(在5.0、5.1、6.0上进行了测试)。再次强调,丑陋的部分是这只是糟糕的、不稳定的代码,而且它很hacky,但在他们发布更好的API之前,我无能为力... 这是hack的方法:
private Boolean mHasEarpiece = null; // intentionally 'B' instead of 'b'

public boolean hasEarpiece() {
    if (mHasEarpiece != null) {
        return mHasEarpiece;
    }

    // not calculated yet, do it now
    try {
        Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE);
        Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE");
        int earpieceFlag = field.getInt(null);
        int bitmaskResult = (int) method.invoke(mAudioManager, AudioManager.STREAM_VOICE_CALL);

        // check if masked by the earpiece flag
        if ((bitmaskResult & earpieceFlag) == earpieceFlag) {
            mHasEarpiece = Boolean.TRUE;
        } else {
            mHasEarpiece = Boolean.FALSE;
        }
    } catch (Throwable error) {
        Log.e(TAG, "Error while checking earpiece! ", error);
        mHasEarpiece = Boolean.FALSE;
    }

    return mHasEarpiece;
}

如果您有更好的改进意见,我非常乐意接受 :)


注意:我没有对它进行过多的调试,但我已经看到这种方法在BLU Advance(Android 5.1)手机上返回了“true”和“false”,该手机肯定有一个听筒。尽管我是与一些混乱的AudioManager遗留代码一起使用此方法,但我认为像DEVICE_OUT_EARPIECE这样的字段应该是静态的并且独立于混乱的代码? - Electro-Bunny
是的,这有点粗糙,不确定为什么会发生这种情况。 - milosmns
我认为这段代码非常不可靠,尤其是现在,在许多不同设备(常见制造商和更“奇特”的设备)和操作系统版本(少数数字,带有自定义修改、干净的gms和vanilla)上进行了许多测试。 - snachmsm

2

我已经调查过了,不幸的是我找不到任何API支持检测耳机是否存在。

现在我已将此问题提交给谷歌作为功能请求:
http://code.google.com/p/android/issues/detail?id=37623

如果您赞同这个功能,请在Google问题页面底部点击星标进行投票!

许多用户提出了一个问题,即Google Talk没有提供扬声器/耳机切换选项,这可能表明缺少合适的API...
http://code.google.com/p/android/issues/detail?id=19221

作为一种解决方法,您可能希望在以下设备上隐藏该功能(所有这些方法都有缺点):

  • 具有超大屏幕的设备(例如平板电脑)
  • 不支持电话功能的设备
  • 使用可配置的白名单/黑名单,列出没有耳机的主要设备。

顺便说一句,如果您使用这个答案将音频路由到耳机,则似乎在Xoom(OS 3.1)或三星Galaxy Tab(OS 2.2)上运行该代码没有任何负面影响 - 音频仍然通过外部扬声器播放。 我还没有测试与蓝牙耳机的任何交互。


我设法破解了它,在我刚刚测试的棒棒糖上运行良好。有关详细信息,请参见我的答案。 - milosmns

1

从 Android S/12(API 31)开始,我们有了一种检查内置听筒是否存在的新方法。以下是一个全面的可复制方法,在旧的系统版本上,它使用了 @milosmns 答案中的反射(谢谢!)

private static Boolean hasEarpiece = null;

public static boolean hasEarpiece(Context context) {
    if (hasEarpiece != null) return hasEarpiece;

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

    if (Build.VERSION.SDK_INT >= 31) {
        List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices();
        for (AudioDeviceInfo device : devices) {
            if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
                hasEarpiece = true;
                break;
            }
        }
        if (hasEarpiece == null) {
            // just for safety
            AudioDeviceInfo currDevice = audioManager.getCommunicationDevice();
            hasEarpiece = currDevice != null &&
                    currDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE;
        }
        Log.i("EarpieceBuiltInChecker", "hasEarpiece detected:%s", hasEarpiece);
        return hasEarpiece;
    }

    try {
        Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE);
        Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE");
        int earpieceFlag = field.getInt(null);
        int bitmaskResult = (int) method.invoke(audioManager, AudioManager.STREAM_VOICE_CALL);

        hasEarpiece = (bitmaskResult & earpieceFlag) == earpieceFlag;
        Log.i("EarpieceBuiltInChecker", "hasEarpiece detected:" + hasEarpiece);
    } catch (Throwable error) {
        hasEarpiece = false;
        Log.i("EarpieceBuiltInChecker", "hasEarpiece detection FAILED!");
    }

    return hasEarpiece;
}

我的流类型是AudioManager.STREAM_VOICE_CALL(适用的情况下)


1

正如您现在所知道的那样,使用当前的Android API无法完成此操作。

音频硬件接口是专有的,即由制造商为其硬件编写,并且必须支持Android系统的某些调用,以使其与使用Android SDK开发的应用程序兼容。

即使您找到了绕过一个设备缺乏API支持的方法,这也可能无法在所有设备上运行。您的AudioManager.setSpeakerphoneOn(false)示例可能非常适用于某些设备,这完全取决于音频硬件接口的编写方式。如果没有听筒,大多数平板电脑将会将耳机和扬声器的音频路由视为相同。

如果您想查看Nexus 7的音频硬件接口代码,请点击这里


-2
public boolean isEarPieceConnected() {
        AudioManager audio = (AudioManager) this
                .getSystemService(Context.AUDIO_SERVICE);
        if (audio.isWiredHeadsetOn()) {
            Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
            return true;
        } else {
            Toast.makeText(this, "Not Connected", Toast.LENGTH_SHORT).show();
            return false;
        }
    }

你也可以检查audio.isSpeakerphoneOn()。

如果我理解你的问题正确,我希望这个方法能够解决它。


2
抱歉,这个问题涉及到的是内置耳机而不是有线耳机。另外,isWiredHeadsetOn()已经过时了,它的文档说明“这并不是判断音频是否真正通过有线耳机输出的有效指标,因为音频路由还取决于其他条件。” - Dan J

-3

3
抱歉,这个问题不涉及有线耳机。原始问题提到了AudioManager中的方法,所以我们已经知道它了。 - Dan J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接