mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-15 08:45:04 +01:00
select audio device (WIP)
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
9b889d232f
commit
9c0fa9acc2
@ -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);
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user