18

I've got an app which displays a speakerphone toggle button when used on a phone. The toggle switches audio routing between the phone's earpiece and the speakerphone. However, when the app is run on a tablet (or any device which lacks an earpiece), I'd like to remove the toggle, since all audio is routed through the speakerphone.

Ideally, I'd like to use some kind of isEarpiecePresent() call, or maybe check a flag on some configuration object to find this information, but I can't find anything of the sort in the API.

I attempted to work around the issue by calling AudioManager.setSpeakerphoneOn(false), then checking AudioManager.isSpeakerphoneOn(), hoping that it would still return true and I could key off of that. The system returned false, even though audio is still routing through the speaker.

I'm currently thinking of checking for telephony capability, even though that doesn't exactly fit. Any other ideas?

Josh
  • 10,618
  • 2
  • 32
  • 36
  • 1
    FYI, I don't think the telephony capability will be reliable, e.g. the Samsung Galaxy Tab that I have supports telephony, but does not have an earpiece speaker. http://en.wikipedia.org/wiki/Samsung_Galaxy_Tab – Dan J Sep 13 '12 at 22:39
  • 1
    Did you check this? http://developer.android.com/training/managing-audio/audio-output.html – Dusko Sep 18 '12 at 09:41
  • Thanks stetocina, but unfortunately there's nothing in those docs regarding the detection of audio output hardware. – Josh Sep 18 '12 at 12:44
  • There also are Galaxy Tab devices out there that don't support telephony (there's no cellular hardware) but have an earpiece, supposedly for VoIP apps. – Grishka Jun 19 '16 at 15:05

6 Answers6

5

So the ugly part is - Google is ignoring the problem. The pretty part is that I managed to actually do the check for earpiece, and on 5/5 devices it worked fine (tested on 5.0, 5.1, 6.0). And again, the ugly part is that this is just bad, unstable code and that it's hacky, but until they publish a better API, I couldn't do much..

Here is the 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;
}

I'm open for improvement edits if you have something better :)

milosmns
  • 3,595
  • 4
  • 36
  • 48
  • Caveat emptor: I have not debugged it much, but I've seen this method return both "true" and "false" on a BLU Advance (Android 5.1) phone which definitely has an earpiece. Granted I was using this method along with some legacy code that mucks about with the AudioManager, but I would think such fields as DEVICE_OUT_EARPIECE would be static and independent of mucking about? – Electro-Bunny Mar 15 '17 at 15:08
  • Yeah, it's sort of hacky, not sure why that happens. – milosmns Mar 15 '17 at 15:10
  • I would say that this code is very NOT reliable, especially nowadays, based on many tests on different devices (common manufacturers and more "exotic" devices) and OS versions (few numbers, with custom mods, clean with gms and vanilla) – snachmsm Sep 29 '22 at 09:43
2

I've researched this and unfortunately I can't find any API support for detecting whether an earpiece exists.

I've now raised this with Google as a feature request:
http://code.google.com/p/android/issues/detail?id=37623

If you agree with the feature, click on the star at the bottom of the Google issue page to vote for it!

Lots of users have raised the issue that Google Talk doesn't offer a speaker/earpiece toggle option, which perhaps suggests the absence of a suitable API...
http://code.google.com/p/android/issues/detail?id=19221

As a workaround you may wish to hide the feature on devices approximated by (all of these approaches have drawbacks):

  • those with extra large screens (i.e. tablets)
  • those with no telephony support
  • use a configurable white/blacklist of the main devices that do not have an earpiece.

FTR, if you use this answer to route audio to the earpiece, there don't seem to be any negative side affects running that code on the Xoom (OS 3.1) or the Samsung Galaxy Tab (OS 2.2) - the audio just continues to play through the external speaker. I haven't had a chance to test if there are any interactions with Bluetooth headsets yet.

Community
  • 1
  • 1
Dan J
  • 25,433
  • 17
  • 100
  • 173
1

As you may now be aware, this cannot be done using the current Android API.

The Audio Hardware interface is proprietary, i.e. written by the manufacturers for their hardware and has to support certain calls by the Android system to make it compatible with applications developed using the Android SDK.

Even if you find a way around the lack of API support for one device this may not work on all devices. Your AudioManager.setSpeakerphoneOn(false) example may very well work for devices as it all depends on how the Audio Hardware interface has been written. It would be my assumption that most tablets will treat audio routing for earpiece and speaker the same if there is no earpiece.

If you want to check out the Audio Hardware interface code for the Nexus 7, it's here

Ne0
  • 2,688
  • 3
  • 35
  • 49
1

starting with Android S/12 (API 31) we have new methods for checking built-in earpiece presence. comprehensive copy-pasteable method below, on older system versions it is using reflection from @milosmns answer (thanks!)

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

my streams are AudioManager.STREAM_VOICE_CALL type (where applicable)

snachmsm
  • 17,866
  • 3
  • 32
  • 74
-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;
        }
    }

you can also check for audio.isSpeakerphoneOn()

I hope this works if i am getting your problem right.

Chandrashekhar
  • 498
  • 4
  • 19
  • 2
    Sorry, no - the question is about the built in earpiece, not wired headsets. In addition, isWiredHeadsetOn() is deprecated, and its docs state "This is not a valid indication that audio playback is actually over the wired headset as audio routing depends on other conditions." – Dan J Sep 20 '12 at 13:59
-3

You can check this by using Audio Manager

Audio Manager

and also look

Head Set Plugged

Bucks
  • 689
  • 3
  • 11
  • 28
  • 3
    Sorry, this question is not about wired headsets. The original question references methods in AudioManager, so we already know about it. – Dan J Sep 20 '12 at 14:02