select audio device (WIP)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-02-01 00:42:44 +01:00
parent 9b889d232f
commit 9c0fa9acc2
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
3 changed files with 77 additions and 143 deletions

View File

@ -32,7 +32,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.MediaPlayer;
@ -84,7 +83,6 @@ import com.nextcloud.talk.models.json.signaling.SignalingOverall;
import com.nextcloud.talk.models.json.signaling.settings.IceServer;
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
import com.nextcloud.talk.ui.dialog.ScopeDialog;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.NotificationUtils;
@ -145,6 +143,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import autodagger.AutoInjector;
import io.reactivex.Observable;
@ -335,17 +334,6 @@ public class CallActivity extends CallBaseActivity {
this
).show());
// binding.audioOutputButton.setOnClickListener(l -> {
// if (audioManager != null) {
// audioManager.toggleUseSpeakerphone();
// if (audioManager.isSpeakerphoneAutoOn()) {
// binding.audioOutputButton.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_up_white_24dp);
// } else {
// binding.audioOutputButton.getHierarchy().setPlaceholderImage(R.drawable.ic_volume_mute_white_24dp);
// }
// }
// });
binding.microphoneButton.setOnClickListener(l -> onMicrophoneClick());
binding.microphoneButton.setOnLongClickListener(l -> {
if (!microphoneOn) {
@ -381,9 +369,31 @@ public class CallActivity extends CallBaseActivity {
});
}
public void setAudioOutputIcon(Drawable drawable){
binding.audioOutputButton.getHierarchy().setPlaceholderImage(drawable);
DrawableCompat.setTint(drawable, Color.WHITE);
public void setAudioOutputChannel(MagicAudioManager.AudioDevice audioDevice) {
if (audioManager == null) {
return;
}
audioManager.selectAudioDevice(audioDevice);
switch (audioManager.getResultingAudioDevice()) {
case BLUETOOTH:
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_bluetooth_audio_24));
break;
case SPEAKER_PHONE:
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
AppCompatResources.getDrawable(context, R.drawable.ic_volume_up_white_24dp));
break;
case EARPIECE:
binding.audioOutputButton.getHierarchy().setPlaceholderImage(
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_phone_in_talk_24));
break;
default:
Log.e(TAG, "Invalid audio device selection");
break;
}
DrawableCompat.setTint(binding.audioOutputButton.getDrawable(), Color.WHITE);
}
private void createCameraEnumerator() {
@ -391,7 +401,7 @@ public class CallActivity extends CallBaseActivity {
try {
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(this);
} catch (final Throwable throwable) {
Log.w(TAG, "Camera2Enumator threw an error");
Log.w(TAG, "Camera2Enumerator threw an error");
}
if (camera2EnumeratorIsSupported) {
@ -726,7 +736,8 @@ public class CallActivity extends CallBaseActivity {
}
private void onAudioManagerDevicesChanged(
final MagicAudioManager.AudioDevice device, final Set<MagicAudioManager.AudioDevice> availableDevices) {
final MagicAudioManager.AudioDevice device,
final Set<MagicAudioManager.AudioDevice> availableDevices) {
Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
+ "selected: " + device);

View File

@ -23,7 +23,6 @@
package com.nextcloud.talk.ui.dialog
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetBehavior
@ -31,6 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.CallActivity
import com.nextcloud.talk.databinding.DialogAudioOutputBinding
import com.nextcloud.talk.webrtc.MagicAudioManager
class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(callActivity) {
@ -44,22 +44,17 @@ class AudioOutputDialog(val callActivity: CallActivity) : BottomSheetDialog(call
dialogAudioOutputBinding.audioOutputBluetooth.setOnClickListener {
Log.d(TAG, "bluetooth button clicked")
callActivity.setAudioOutputIcon(dialogAudioOutputBinding.audioOutputBluetoothIcon.drawable)
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.BLUETOOTH)
dismiss()
}
dialogAudioOutputBinding.audioOutputSpeaker.setOnClickListener {
Log.d(TAG, "speaker button clicked")
callActivity.setAudioOutputIcon(dialogAudioOutputBinding.audioOutputSpeakerIcon.drawable)
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.SPEAKER_PHONE)
dismiss()
}
dialogAudioOutputBinding.audioOutputEarspeaker.setOnClickListener {
Log.d(TAG, "earspeaker button clicked")
callActivity.setAudioOutputIcon(dialogAudioOutputBinding.audioOutputEarspeakerIcon.drawable)
callActivity.setAudioOutputChannel(MagicAudioManager.AudioDevice.EARPIECE)
dismiss()
}
}

View File

@ -55,13 +55,9 @@ import java.util.Set;
*/
public class MagicAudioManager {
private static final String TAG = "MagicAudioManager";
private static final String SPEAKERPHONE_AUTO = "auto";
private static final String SPEAKERPHONE_FALSE = "false";
private final Context magicContext;
// Handles all tasks related to Bluetooth headset devices.
private final MagicBluetoothManager bluetoothManager;
// Contains speakerphone setting: auto, true or false
private String useSpeakerphone;
private boolean controlSpeakerByProximitySensor;
private AudioManager audioManager;
private AudioManagerEvents audioManagerEvents;
private AudioManagerState amState;
@ -69,31 +65,15 @@ public class MagicAudioManager {
private boolean savedIsSpeakerPhoneOn = false;
private boolean savedIsMicrophoneMute = false;
private boolean hasWiredHeadset = false;
// Default audio device; speaker phone for video calls or earpiece for audio
// only calls.
private AudioDevice defaultAudioDevice;
// Contains the currently selected audio device.
// This device is changed automatically using a certain scheme where e.g.
// a wired headset "wins" over speaker phone. It is also possible for a
// user to explicitly select a device (and overrid any predefined scheme).
// See |userSelectedAudioDevice| for details.
private AudioDevice selectedAudioDevice;
// Contains the user-selected audio device which overrides the predefined
// selection scheme.
// TODO(henrika): always set to AudioDevice.NONE today. Add support for
// explicit selection based on choice by userSelectedAudioDevice.
private AudioDevice userSelectedAudioDevice;
// Proximity sensor object. It measures the proximity of an object in cm
// relative to the view screen of a device and can therefore be used to
// assist device switching (close to ear <=> use headset earpiece if
// available, far from ear <=> use speaker phone).
private AudioDevice resultingAudioDevice;
private MagicProximitySensor proximitySensor = null;
// Contains a list of available audio devices. A Set collection is used to
// avoid duplicate elements.
private Set<AudioDevice> audioDevices = new HashSet<>();
// Broadcast receiver for wired headset intent broadcasts.
private BroadcastReceiver wiredHeadsetReceiver;
// Callback method for changes in audio focus.
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
private PowerManagerUtils powerManagerUtils;
@ -110,18 +90,8 @@ public class MagicAudioManager {
powerManagerUtils = new PowerManagerUtils();
powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITH_PROXIMITY_SENSOR_LOCK);
if (useProximitySensor) {
useSpeakerphone = SPEAKERPHONE_AUTO;
} else {
useSpeakerphone = SPEAKERPHONE_FALSE;
}
if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) {
defaultAudioDevice = AudioDevice.EARPIECE;
} else {
defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
}
controlSpeakerByProximitySensor = useProximitySensor;
updateAudioDeviceState();
// Create and initialize the proximity sensor.
// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
@ -134,8 +104,6 @@ public class MagicAudioManager {
onProximitySensorChangedState();
}
});
Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice);
}
/**
@ -145,29 +113,13 @@ public class MagicAudioManager {
return new MagicAudioManager(context, useProximitySensor);
}
public void toggleUseSpeakerphone() {
if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) {
useSpeakerphone = SPEAKERPHONE_AUTO;
setDefaultAudioDevice(AudioDevice.SPEAKER_PHONE);
} else {
useSpeakerphone = SPEAKERPHONE_FALSE;
setDefaultAudioDevice(AudioDevice.EARPIECE);
}
updateAudioDeviceState();
}
public boolean isSpeakerphoneAutoOn() {
return (useSpeakerphone.equals(SPEAKERPHONE_AUTO));
}
/**
* This method is called when the proximity sensor reports a state change,
* e.g. from "NEAR to FAR" or from "FAR to NEAR".
*/
private void onProximitySensorChangedState() {
if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) {
if (!controlSpeakerByProximitySensor) {
return;
}
@ -274,7 +226,7 @@ public class MagicAudioManager {
// Set initial device states.
userSelectedAudioDevice = AudioDevice.NONE;
selectedAudioDevice = AudioDevice.NONE;
resultingAudioDevice = AudioDevice.NONE;
audioDevices.clear();
// Initialize and start Bluetooth if a BT device is available or initiate
@ -355,35 +307,10 @@ public class MagicAudioManager {
Log.e(TAG, "Invalid audio device selection");
break;
}
selectedAudioDevice = device;
resultingAudioDevice = device;
}
}
/**
* Changes default audio device.
* TODO(henrika): add usage of this method in the AppRTCMobile client.
*/
public void setDefaultAudioDevice(AudioDevice defaultDevice) {
ThreadUtils.checkIsOnMainThread();
switch (defaultDevice) {
case SPEAKER_PHONE:
defaultAudioDevice = defaultDevice;
break;
case EARPIECE:
if (hasEarpiece()) {
defaultAudioDevice = defaultDevice;
} else {
defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
}
break;
default:
Log.e(TAG, "Invalid default audio device selection");
break;
}
Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
updateAudioDeviceState();
}
/**
* Changes selection of the currently active audio device.
*/
@ -392,7 +319,15 @@ public class MagicAudioManager {
if (!audioDevices.contains(device)) {
Log.e(TAG, "Can not select " + device + " from available " + audioDevices);
}
userSelectedAudioDevice = device;
if (device == AudioDevice.SPEAKER_PHONE) {
controlSpeakerByProximitySensor = true;
} else {
controlSpeakerByProximitySensor = false;
}
updateAudioDeviceState();
}
@ -407,9 +342,9 @@ public class MagicAudioManager {
/**
* Returns the currently selected audio device.
*/
public AudioDevice getSelectedAudioDevice() {
public AudioDevice getResultingAudioDevice() {
ThreadUtils.checkIsOnMainThread();
return selectedAudioDevice;
return resultingAudioDevice;
}
/**
@ -482,10 +417,6 @@ public class MagicAudioManager {
}
}
/**
* Updates list of possible audio devices and make new device selection.
* TODO(henrika): add unit test to verify all state transitions.
*/
public void updateAudioDeviceState() {
ThreadUtils.checkIsOnMainThread();
Log.d(TAG, "--- updateAudioDeviceState: "
@ -493,19 +424,18 @@ public class MagicAudioManager {
+ "BT state=" + bluetoothManager.getState());
Log.d(TAG, "Device status: "
+ "available=" + audioDevices + ", "
+ "selected=" + selectedAudioDevice + ", "
+ "resulting(current)=" + resultingAudioDevice + ", "
+ "user selected=" + userSelectedAudioDevice);
// Check if any Bluetooth headset is connected. The internal BT state will
// change accordingly.
// TODO(henrika): perhaps wrap required state into BT manager.
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
bluetoothManager.updateDevice();
}
// Update the set of available audio devices.
Set<AudioDevice> newAudioDevices = new HashSet<>();
if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED
@ -518,34 +448,32 @@ public class MagicAudioManager {
// If a wired headset is connected, then it is the only possible option.
newAudioDevices.add(AudioDevice.WIRED_HEADSET);
} else {
// No wired headset, hence the audio-device list can contain speaker
// phone (on a tablet), or speaker phone and earpiece (on mobile phone).
newAudioDevices.add(AudioDevice.SPEAKER_PHONE);
if (hasEarpiece()) {
newAudioDevices.add(AudioDevice.EARPIECE);
}
}
// Store state which is set to true if the device list has changed.
boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
// Update the existing audio device set.
audioDevices = newAudioDevices;
// Correct user selected audio devices if needed.
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
&& userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
// If BT is not available, it can't be the user selection.
if (userSelectedAudioDevice == AudioDevice.BLUETOOTH
&& bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE) {
userSelectedAudioDevice = AudioDevice.NONE;
}
if (hasWiredHeadset && userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE) {
// If user selected speaker phone, but then plugged wired headset then make
// wired headset as user selected device.
if (userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE && hasWiredHeadset) {
userSelectedAudioDevice = AudioDevice.WIRED_HEADSET;
}
if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET) {
// If user selected wired headset, but then unplugged wired headset then make
// speaker phone as user selected device.
if (userSelectedAudioDevice == AudioDevice.WIRED_HEADSET && !hasWiredHeadset) {
userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
}
// Need to start Bluetooth if it is available and user either selected it explicitly or
// user did not select any output device.
boolean needBluetoothAudioStart =
@ -586,34 +514,34 @@ public class MagicAudioManager {
// Update selected audio device.
AudioDevice newAudioDevice = selectedAudioDevice;
AudioDevice newResultingAudioDevice;
if (bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
// If a Bluetooth is connected, then it should be used as output audio
// device. Note that it is not sufficient that a headset is available;
// an active SCO channel must also be up and running.
newAudioDevice = AudioDevice.BLUETOOTH;
newResultingAudioDevice = AudioDevice.BLUETOOTH;
} else if (hasWiredHeadset) {
// If a wired headset is connected, but Bluetooth is not, then wired headset is used as
// audio device.
newAudioDevice = AudioDevice.WIRED_HEADSET;
newResultingAudioDevice = AudioDevice.WIRED_HEADSET;
} else {
// No wired headset and no Bluetooth, hence the audio-device list can contain speaker
// phone (on a tablet), or speaker phone and earpiece (on mobile phone).
// |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or AudioDevice.EARPIECE
// depending on the user's selection.
newAudioDevice = defaultAudioDevice;
newResultingAudioDevice = userSelectedAudioDevice;
}
// Switch to new device but only if there has been any changes.
if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
if (newResultingAudioDevice != resultingAudioDevice || audioDeviceSetUpdated) {
// Do the required device switch.
setAudioDeviceInternal(newAudioDevice);
setAudioDeviceInternal(newResultingAudioDevice);
Log.d(TAG, "New device status: "
+ "available=" + audioDevices + ", "
+ "selected=" + newAudioDevice);
+ "resulting(new)=" + newResultingAudioDevice);
if (audioManagerEvents != null) {
// Notify a listening client that audio device has been changed.
audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices);
audioManagerEvents.onAudioDeviceChanged(resultingAudioDevice, audioDevices);
}
}
Log.d(TAG, "--- updateAudioDeviceState done");