do not enforce main thread for getting audio devices

Daniel Gultsch created

fixes #206

Change summary

src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java | 339 
1 file changed, 155 insertions(+), 184 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java 🔗

@@ -15,42 +15,31 @@ import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
 import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.MediaRecorder;
-import android.os.Build;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
+import com.google.common.collect.ImmutableSet;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.AppRTCUtils;
+
 import org.webrtc.ThreadUtils;
 
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
 
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.utils.AppRTCUtils;
-import eu.siacs.conversations.xmpp.jingle.Media;
-
-/**
- * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
- */
+/** AppRTCAudioManager manages all audio related parts of the AppRTC demo. */
 public class AppRTCAudioManager {
 
-    private static CountDownLatch microphoneLatch;
-
     private final Context apprtcContext;
     // Contains speakerphone setting: auto, true or false
     // Handles all tasks related to Bluetooth headset devices.
     private final AppRTCBluetoothManager bluetoothManager;
-    @Nullable
-    private final AudioManager audioManager;
-    @Nullable
-    private AudioManagerEvents audioManagerEvents;
+    @Nullable private final AudioManager audioManager;
+    @Nullable private AudioManagerEvents audioManagerEvents;
     private AudioManagerState amState;
     private boolean savedIsSpeakerPhoneOn;
     private boolean savedIsMicrophoneMute;
@@ -76,8 +65,7 @@ public class AppRTCAudioManager {
     // Broadcast receiver for wired headset intent broadcasts.
     private final BroadcastReceiver wiredHeadsetReceiver;
     // Callback method for changes in audio focus.
-    @Nullable
-    private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
+    @Nullable private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
 
     public AppRTCAudioManager(final Context context) {
         apprtcContext = context;
@@ -95,7 +83,6 @@ public class AppRTCAudioManager {
         AppRTCUtils.logDeviceInfo(Config.LOGTAG);
     }
 
-
     @SuppressWarnings("deprecation")
     public void start(final AudioManagerEvents audioManagerEvents) {
         Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".start()");
@@ -104,7 +91,6 @@ public class AppRTCAudioManager {
             Log.e(Config.LOGTAG, "AudioManager is already active");
             return;
         }
-        awaitMicrophoneLatch();
         this.audioManagerEvents = audioManagerEvents;
         amState = AudioManagerState.RUNNING;
         // Store current audio state so we can restore it when stop() is called.
@@ -112,48 +98,45 @@ public class AppRTCAudioManager {
         savedIsMicrophoneMute = audioManager.isMicrophoneMute();
         hasWiredHeadset = hasWiredHeadset();
         // Create an AudioManager.OnAudioFocusChangeListener instance.
-        audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
-            // Called on the listener to notify if the audio focus for this listener has been changed.
-            // The |focusChange| value indicates whether the focus was gained, whether the focus was lost,
-            // and whether that loss is transient, or whether the new focus holder will hold it for an
-            // unknown amount of time.
-            // TODO(henrika): possibly extend support of handling audio-focus changes. Only contains
-            // logging for now.
-            @Override
-            public void onAudioFocusChange(int focusChange) {
-                final String typeOfChange;
-                switch (focusChange) {
-                    case AudioManager.AUDIOFOCUS_GAIN:
-                        typeOfChange = "AUDIOFOCUS_GAIN";
-                        break;
-                    case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
-                        typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT";
-                        break;
-                    case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
-                        typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE";
-                        break;
-                    case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
-                        typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK";
-                        break;
-                    case AudioManager.AUDIOFOCUS_LOSS:
-                        typeOfChange = "AUDIOFOCUS_LOSS";
-                        break;
-                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                        typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT";
-                        break;
-                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                        typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK";
-                        break;
-                    default:
-                        typeOfChange = "AUDIOFOCUS_INVALID";
-                        break;
-                }
-                Log.d(Config.LOGTAG, "onAudioFocusChange: " + typeOfChange);
-            }
-        };
+        audioFocusChangeListener =
+                new AudioManager.OnAudioFocusChangeListener() {
+                    // Called on the listener to notify if the audio focus for this listener has
+                    // been changed.
+                    // The |focusChange| value indicates whether the focus was gained, whether the
+                    // focus was lost,
+                    // and whether that loss is transient, or whether the new focus holder will hold
+                    // it for an
+                    // unknown amount of time.
+                    // TODO(henrika): possibly extend support of handling audio-focus changes. Only
+                    // contains
+                    // logging for now.
+                    @Override
+                    public void onAudioFocusChange(final int focusChange) {
+                        final String typeOfChange =
+                                switch (focusChange) {
+                                    case AudioManager.AUDIOFOCUS_GAIN -> "AUDIOFOCUS_GAIN";
+                                    case AudioManager
+                                            .AUDIOFOCUS_GAIN_TRANSIENT -> "AUDIOFOCUS_GAIN_TRANSIENT";
+                                    case AudioManager
+                                            .AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE";
+                                    case AudioManager
+                                            .AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -> "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK";
+                                    case AudioManager.AUDIOFOCUS_LOSS -> "AUDIOFOCUS_LOSS";
+                                    case AudioManager
+                                            .AUDIOFOCUS_LOSS_TRANSIENT -> "AUDIOFOCUS_LOSS_TRANSIENT";
+                                    case AudioManager
+                                            .AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK";
+                                    default -> "AUDIOFOCUS_INVALID";
+                                };
+                        Log.d(Config.LOGTAG, "onAudioFocusChange: " + typeOfChange);
+                    }
+                };
         // Request audio playout focus (without ducking) and install listener for changes in focus.
-        int result = audioManager.requestAudioFocus(audioFocusChangeListener,
-                AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+        int result =
+                audioManager.requestAudioFocus(
+                        audioFocusChangeListener,
+                        AudioManager.STREAM_VOICE_CALL,
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
         if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
             Log.d(Config.LOGTAG, "Audio focus request granted for VOICE_CALL streams");
         } else {
@@ -182,21 +165,9 @@ public class AppRTCAudioManager {
         Log.d(Config.LOGTAG, "AudioManager started");
     }
 
-    private void awaitMicrophoneLatch() {
-        final CountDownLatch latch = microphoneLatch;
-        if (latch == null) {
-            return;
-        }
-        try {
-            latch.await();
-        } catch (InterruptedException e) {
-            //ignore
-        }
-    }
-
     @SuppressWarnings("deprecation")
     public void stop() {
-        Log.d(Config.LOGTAG,"appRtpAudioManager.stop()");
+        Log.d(Config.LOGTAG, "appRtpAudioManager.stop()");
         Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".stop()");
         ThreadUtils.checkIsOnMainThread();
         if (amState != AudioManagerState.RUNNING) {
@@ -214,60 +185,44 @@ public class AppRTCAudioManager {
         audioManager.abandonAudioFocus(audioFocusChangeListener);
         audioFocusChangeListener = null;
         audioManagerEvents = null;
-        Log.d(Config.LOGTAG,"appRtpAudioManager.stopped()");
+        Log.d(Config.LOGTAG, "appRtpAudioManager.stopped()");
     }
 
-    /**
-     * Changes selection of the currently active audio device.
-     */
-    private void setAudioDeviceInternal(CallIntegration.AudioDevice device) {
+    /** Changes selection of the currently active audio device. */
+    private void setAudioDeviceInternal(final CallIntegration.AudioDevice device) {
         Log.d(Config.LOGTAG, "setAudioDeviceInternal(device=" + device + ")");
         AppRTCUtils.assertIsTrue(audioDevices.contains(device));
         switch (device) {
-            case SPEAKER_PHONE:
-                setSpeakerphoneOn(true);
-                break;
-            case EARPIECE:
-            case WIRED_HEADSET:
-            case BLUETOOTH:
-                setSpeakerphoneOn(false);
-                break;
-            default:
-                Log.e(Config.LOGTAG, "Invalid audio device selection");
-                break;
+            case SPEAKER_PHONE -> setSpeakerphoneOn(true);
+            case EARPIECE, WIRED_HEADSET, BLUETOOTH -> setSpeakerphoneOn(false);
+            default -> Log.e(Config.LOGTAG, "Invalid audio device selection");
         }
         selectedAudioDevice = device;
     }
 
     /**
-     * Changes default audio device.
-     * TODO(henrika): add usage of this method in the AppRTCMobile client.
+     * Changes default audio device. TODO(henrika): add usage of this method in the AppRTCMobile
+     * client.
      */
-    public void setDefaultAudioDevice(CallIntegration.AudioDevice defaultDevice) {
+    public void setDefaultAudioDevice(final CallIntegration.AudioDevice defaultDevice) {
         ThreadUtils.checkIsOnMainThread();
         switch (defaultDevice) {
-            case SPEAKER_PHONE:
-                defaultAudioDevice = defaultDevice;
-                break;
-            case EARPIECE:
+            case SPEAKER_PHONE -> defaultAudioDevice = defaultDevice;
+            case EARPIECE -> {
                 if (hasEarpiece()) {
                     defaultAudioDevice = defaultDevice;
                 } else {
                     defaultAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
                 }
-                break;
-            default:
-                Log.e(Config.LOGTAG, "Invalid default audio device selection");
-                break;
+            }
+            default -> Log.e(Config.LOGTAG, "Invalid default audio device selection");
         }
         Log.d(Config.LOGTAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
         updateAudioDeviceState();
     }
 
-    /**
-     * Changes selection of the currently active audio device.
-     */
-    public void selectAudioDevice(CallIntegration.AudioDevice device) {
+    /** Changes selection of the currently active audio device. */
+    public void selectAudioDevice(final CallIntegration.AudioDevice device) {
         ThreadUtils.checkIsOnMainThread();
         if (!audioDevices.contains(device)) {
             Log.e(Config.LOGTAG, "Can not select " + device + " from available " + audioDevices);
@@ -276,38 +231,27 @@ public class AppRTCAudioManager {
         updateAudioDeviceState();
     }
 
-    /**
-     * Returns current set of available/selectable audio devices.
-     */
+    /** Returns current set of available/selectable audio devices. */
     public Set<CallIntegration.AudioDevice> getAudioDevices() {
-        ThreadUtils.checkIsOnMainThread();
-        return Collections.unmodifiableSet(new HashSet<>(audioDevices));
+        return ImmutableSet.copyOf(audioDevices);
     }
 
-    /**
-     * Returns the currently selected audio device.
-     */
+    /** Returns the currently selected audio device. */
     public CallIntegration.AudioDevice getSelectedAudioDevice() {
         return selectedAudioDevice;
     }
 
-    /**
-     * Helper method for receiver registration.
-     */
+    /** Helper method for receiver registration. */
     private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
         apprtcContext.registerReceiver(receiver, filter);
     }
 
-    /**
-     * Helper method for unregistration of an existing receiver.
-     */
+    /** Helper method for unregistration of an existing receiver. */
     private void unregisterReceiver(BroadcastReceiver receiver) {
         apprtcContext.unregisterReceiver(receiver);
     }
 
-    /**
-     * Sets the speaker phone mode.
-     */
+    /** Sets the speaker phone mode. */
     private void setSpeakerphoneOn(boolean on) {
         boolean wasOn = audioManager.isSpeakerphoneOn();
         if (wasOn == on) {
@@ -316,9 +260,7 @@ public class AppRTCAudioManager {
         audioManager.setSpeakerphoneOn(on);
     }
 
-    /**
-     * Sets the microphone mute state.
-     */
+    /** Sets the microphone mute state. */
     private void setMicrophoneMute(boolean on) {
         boolean wasMuted = audioManager.isMicrophoneMute();
         if (wasMuted == on) {
@@ -327,53 +269,57 @@ public class AppRTCAudioManager {
         audioManager.setMicrophoneMute(on);
     }
 
-    /**
-     * Gets the current earpiece state.
-     */
+    /** Gets the current earpiece state. */
     private boolean hasEarpiece() {
         return apprtcContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
     }
 
     /**
-     * Checks whether a wired headset is connected or not.
-     * This is not a valid indication that audio playback is actually over
-     * the wired headset as audio routing depends on other conditions. We
-     * only use it as an early indicator (during initialization) of an attached
-     * wired headset.
+     * Checks whether a wired headset is connected or not. This is not a valid indication that audio
+     * playback is actually over the wired headset as audio routing depends on other conditions. We
+     * only use it as an early indicator (during initialization) of an attached wired headset.
      */
     @Deprecated
     private boolean hasWiredHeadset() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            return audioManager.isWiredHeadsetOn();
-        } else {
-            final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
-            for (AudioDeviceInfo device : devices) {
-                final int type = device.getType();
-                if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
-                    Log.d(Config.LOGTAG, "hasWiredHeadset: found wired headset");
-                    return true;
-                } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
-                    Log.d(Config.LOGTAG, "hasWiredHeadset: found USB audio device");
-                    return true;
-                }
+        final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+        for (AudioDeviceInfo device : devices) {
+            final int type = device.getType();
+            if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
+                Log.d(Config.LOGTAG, "hasWiredHeadset: found wired headset");
+                return true;
+            } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
+                Log.d(Config.LOGTAG, "hasWiredHeadset: found USB audio device");
+                return true;
             }
-            return false;
         }
+        return false;
     }
 
     /**
-     * Updates list of possible audio devices and make new device selection.
-     * TODO(henrika): add unit test to verify all state transitions.
+     * 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(Config.LOGTAG, "--- updateAudioDeviceState: "
-                + "wired headset=" + hasWiredHeadset + ", "
-                + "BT state=" + bluetoothManager.getState());
-        Log.d(Config.LOGTAG, "Device status: "
-                + "available=" + audioDevices + ", "
-                + "selected=" + selectedAudioDevice + ", "
-                + "user selected=" + userSelectedAudioDevice);
+        Log.d(
+                Config.LOGTAG,
+                "--- updateAudioDeviceState: "
+                        + "wired headset="
+                        + hasWiredHeadset
+                        + ", "
+                        + "BT state="
+                        + bluetoothManager.getState());
+        Log.d(
+                Config.LOGTAG,
+                "Device status: "
+                        + "available="
+                        + audioDevices
+                        + ", "
+                        + "selected="
+                        + selectedAudioDevice
+                        + ", "
+                        + "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.
@@ -410,12 +356,14 @@ public class AppRTCAudioManager {
             // If BT is not available, it can't be the user selection.
             userSelectedAudioDevice = CallIntegration.AudioDevice.NONE;
         }
-        if (hasWiredHeadset && userSelectedAudioDevice == CallIntegration.AudioDevice.SPEAKER_PHONE) {
+        if (hasWiredHeadset
+                && userSelectedAudioDevice == CallIntegration.AudioDevice.SPEAKER_PHONE) {
             // If user selected speaker phone, but then plugged wired headset then make
             // wired headset as user selected device.
             userSelectedAudioDevice = CallIntegration.AudioDevice.WIRED_HEADSET;
         }
-        if (!hasWiredHeadset && userSelectedAudioDevice == CallIntegration.AudioDevice.WIRED_HEADSET) {
+        if (!hasWiredHeadset
+                && userSelectedAudioDevice == CallIntegration.AudioDevice.WIRED_HEADSET) {
             // If user selected wired headset, but then unplugged wired headset then make
             // speaker phone as user selected device.
             userSelectedAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
@@ -425,20 +373,30 @@ public class AppRTCAudioManager {
         boolean needBluetoothAudioStart =
                 bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
                         && (userSelectedAudioDevice == CallIntegration.AudioDevice.NONE
-                        || userSelectedAudioDevice == CallIntegration.AudioDevice.BLUETOOTH);
+                                || userSelectedAudioDevice
+                                        == CallIntegration.AudioDevice.BLUETOOTH);
         // Need to stop Bluetooth audio if user selected different device and
         // Bluetooth SCO connection is established or in the process.
         boolean needBluetoothAudioStop =
                 (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
-                        || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING)
+                                || bluetoothManager.getState()
+                                        == AppRTCBluetoothManager.State.SCO_CONNECTING)
                         && (userSelectedAudioDevice != CallIntegration.AudioDevice.NONE
-                        && userSelectedAudioDevice != CallIntegration.AudioDevice.BLUETOOTH);
+                                && userSelectedAudioDevice
+                                        != CallIntegration.AudioDevice.BLUETOOTH);
         if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
                 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
                 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) {
-            Log.d(Config.LOGTAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
-                    + "stop=" + needBluetoothAudioStop + ", "
-                    + "BT state=" + bluetoothManager.getState());
+            Log.d(
+                    Config.LOGTAG,
+                    "Need BT audio: start="
+                            + needBluetoothAudioStart
+                            + ", "
+                            + "stop="
+                            + needBluetoothAudioStop
+                            + ", "
+                            + "BT state="
+                            + bluetoothManager.getState());
         }
         // Start or stop Bluetooth SCO connection given states set earlier.
         if (needBluetoothAudioStop) {
@@ -467,7 +425,8 @@ public class AppRTCAudioManager {
         } 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
+            // |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or
+            // AudioDevice.EARPIECE
             // depending on the user's selection.
             newAudioDevice = defaultAudioDevice;
         }
@@ -475,9 +434,14 @@ public class AppRTCAudioManager {
         if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
             // Do the required device switch.
             setAudioDeviceInternal(newAudioDevice);
-            Log.d(Config.LOGTAG, "New device status: "
-                    + "available=" + audioDevices + ", "
-                    + "selected=" + newAudioDevice);
+            Log.d(
+                    Config.LOGTAG,
+                    "New device status: "
+                            + "available="
+                            + audioDevices
+                            + ", "
+                            + "selected="
+                            + newAudioDevice);
             if (audioManagerEvents != null) {
                 // Notify a listening client that audio device has been changed.
                 audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices);
@@ -490,22 +454,19 @@ public class AppRTCAudioManager {
         ContextCompat.getMainExecutor(apprtcContext).execute(runnable);
     }
 
-    /**
-     * AudioManager state.
-     */
+    /** AudioManager state. */
     public enum AudioManagerState {
         UNINITIALIZED,
         PREINITIALIZED,
         RUNNING,
     }
 
-    /**
-     * Selected audio device change event.
-     */
+    /** Selected audio device change event. */
     public interface AudioManagerEvents {
         // Callback fired once audio device is changed or list of available audio devices changed.
         void onAudioDeviceChanged(
-                CallIntegration.AudioDevice selectedAudioDevice, Set<CallIntegration.AudioDevice> availableAudioDevices);
+                CallIntegration.AudioDevice selectedAudioDevice,
+                Set<CallIntegration.AudioDevice> availableAudioDevices);
     }
 
     /* Receiver which handles changes in wired headset availability. */
@@ -520,13 +481,23 @@ public class AppRTCAudioManager {
             int state = intent.getIntExtra("state", STATE_UNPLUGGED);
             int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
             String name = intent.getStringExtra("name");
-            Log.d(Config.LOGTAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": "
-                    + "a=" + intent.getAction() + ", s="
-                    + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m="
-                    + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb="
-                    + isInitialStickyBroadcast());
+            Log.d(
+                    Config.LOGTAG,
+                    "WiredHeadsetReceiver.onReceive"
+                            + AppRTCUtils.getThreadInfo()
+                            + ": "
+                            + "a="
+                            + intent.getAction()
+                            + ", s="
+                            + (state == STATE_UNPLUGGED ? "unplugged" : "plugged")
+                            + ", m="
+                            + (microphone == HAS_MIC ? "mic" : "no mic")
+                            + ", n="
+                            + name
+                            + ", sb="
+                            + isInitialStickyBroadcast());
             hasWiredHeadset = (state == STATE_PLUGGED);
             updateAudioDeviceState();
         }
     }
-}
+}