Pārlūkot izejas kodu

select audio device (WIP)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe 3 gadi atpakaļ
vecāks
revīzija
9c0fa9acc2

+ 29 - 18
app/src/main/java/com/nextcloud/talk/activities/CallActivity.java

@@ -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);
 

+ 4 - 9
app/src/main/java/com/nextcloud/talk/ui/dialog/AudioOutputDialog.kt

@@ -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()
         }
     }

+ 44 - 116
app/src/main/java/com/nextcloud/talk/webrtc/MagicAudioManager.java

@@ -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,33 +307,8 @@ public class MagicAudioManager {
                     Log.e(TAG, "Invalid audio device selection");
                     break;
             }
-            selectedAudioDevice = 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;
+            resultingAudioDevice = device;
         }
-        Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
-        updateAudioDeviceState();
     }
 
     /**
@@ -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");