4

I'm developing a mini native (Java) SDK to turn ON/OFF speaker for a react-native app for calls , because React-Native Incall Manager doesn't work properly on Android 12.

Module is working good on Android 11, but since setSpeakerOn method from AudioManager is deprecated for newest SDK, on Android 12 there are some issues. I took the code snippet from official docs and adapted it. Even if boolean result = audioManager.setCommunicationDevice(device) returns TRUE , it doesn't change the speaker state.

Permission

android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

AudioModule.java

public class AudioModule extends ReactContextBaseJavaModule {
    private AudioManager audioManager;
    private static final String TAG = "AudioModule";
    private static ReactApplicationContext reactContext;
AudioModule(ReactApplicationContext context) {
    super(context);
    reactContext = context;
    audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));

}


@Override
public String getName() {
    return "AudioModule";
}

@ReactMethod
public void setSpeakerState(Boolean speakerState) {
    try {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            if (speakerState) {
                setCommunicationDevice(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
            } else {
                audioManager.clearCommunicationDevice(); //turn off speaker
            }
        } else {
            audioManager.setMode(speakerState ? AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_NORMAL);
            audioManager.setSpeakerphoneOn(speakerState);

        }
        Toast.makeText(reactContext, "speakerStatus " + audioManager.isSpeakerphoneOn(), Toast.LENGTH_LONG).show();

    } catch (Exception e) {
        Log.d(TAG, "AudioModule setSpeaker", e);
    }
}


@RequiresApi(api = Build.VERSION_CODES.S)
public void setCommunicationDevice(Integer targetDeviceType) {
    List<AudioDeviceInfo> audioDevices = audioManager.getAvailableCommunicationDevices();
    for (AudioDeviceInfo device : audioDevices) {
        if (device.getType() == targetDeviceType) {
            audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION );
            boolean result = audioManager.setCommunicationDevice(device);

            break;
        }
    }
}

Note: Native Module for React-Native is working properly on Android 11 OS and is showing Toast on Android 12 and 13 (tested on physical devices)

UPDATED: Tried also with IncallService interface: import android.telecom.InCallService;

public class CallService extends InCallService {
private static CallService sInstance;

CallService() {
    sInstance = this;
}


public static CallService getInstance() {
    return sInstance;
}
}

Method called

@ReactMethod
public void toggleSpeaker() {

    boolean isSpeakerOn = audioManager.isSpeakerphoneOn();
    int earpiece = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
    int speaker = CallAudioState.ROUTE_SPEAKER;

    if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.P) {
        callService.getInstance().setAudioRoute(isSpeakerOn ? earpiece : speaker);
    } else {
        audioManager.setSpeakerphoneOn(!isSpeakerOn);
    }
    Toast.makeText(reactContext, "speakerStatus " + audioManager.isSpeakerphoneOn(), Toast.LENGTH_LONG).show();

}

Logcat returns the next message:

android.hardware.audio.service: Failed to fetch the lookup information of the device

2
  • have you found any solution? Commented Sep 30, 2022 at 14:32
  • @SujithSManjavana not yet Commented Oct 2, 2022 at 16:41

1 Answer 1

1

I had similar issue with android 12 and even with some android 11 devices. When I call am.setCommunicationDevice(earpieceDevice) it was returning true but the audio was still playing on loudspeaker. Then I force enabled NORMAL_MODE by calling am.setMode(AudioManager.MODE_NORMAL), and now it's working. Weird!

private fun setAudioDevice(){
 am.mode = AudioManager.MODE_NORMAL
 val speakerDevice: AudioDeviceInfo? = getAudioDevice(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
 am.setCommunicationDevice(speakerDevice)
}

 private fun getAudioDevice(type: Int): AudioDeviceInfo? {
        val audioDevices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
        for (deviceInfo in audioDevices) {
            if (type == deviceInfo.type) return deviceInfo
        }
        return null
    }
Sign up to request clarification or add additional context in comments.

19 Comments

how about to turn on the speaker :) ?
Just get the ``` AudioDeviceInfo``` of your speaker device and call am.setCommunicationDevice(yourSpeakerDevice)
@andresshoyo Be careful though, On my Samsung android 11 device the microphone volume was very low when I use AudioManager.MODE_NORMAL.
@andresshoyo I didn’t notice any microphone issues with android 12 devices. So, Currently, I'm using AudioManager.MODE_NORMAL only on android 12 and higher, and AudioManager.MODE_IN_COMMUNICATION on older devices. Not the best fix. If you find another fix, please let me know.
Same with Xiaomi 12 Lite (Android 12) when use AudioManager.MODE_NORMAL the volume heard in earbud is very low and still takes audio input from microphone of the phone. I'm guessing target api 31 is released in a hurry and most manufacturers didn't adapt to the new api well.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.