1/*
2 * Copyright 2014 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10package eu.siacs.conversations.services;
11
12import android.content.BroadcastReceiver;
13import android.content.Context;
14import android.content.Intent;
15import android.content.IntentFilter;
16import android.content.pm.PackageManager;
17import android.media.AudioDeviceInfo;
18import android.media.AudioFormat;
19import android.media.AudioManager;
20import android.media.AudioRecord;
21import android.media.MediaRecorder;
22import android.os.Build;
23import android.util.Log;
24
25import androidx.annotation.Nullable;
26import androidx.core.content.ContextCompat;
27
28import org.webrtc.ThreadUtils;
29
30import java.util.Collections;
31import java.util.HashSet;
32import java.util.Set;
33import java.util.concurrent.CountDownLatch;
34
35import eu.siacs.conversations.Config;
36import eu.siacs.conversations.utils.AppRTCUtils;
37import eu.siacs.conversations.xmpp.jingle.Media;
38
39/**
40 * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
41 */
42public class AppRTCAudioManager {
43
44 private static CountDownLatch microphoneLatch;
45
46 private final Context apprtcContext;
47 // Contains speakerphone setting: auto, true or false
48 // Handles all tasks related to Bluetooth headset devices.
49 private final AppRTCBluetoothManager bluetoothManager;
50 @Nullable
51 private final AudioManager audioManager;
52 @Nullable
53 private AudioManagerEvents audioManagerEvents;
54 private AudioManagerState amState;
55 private boolean savedIsSpeakerPhoneOn;
56 private boolean savedIsMicrophoneMute;
57 private boolean hasWiredHeadset;
58 // Default audio device; speaker phone for video calls or earpiece for audio
59 // only calls.
60 private CallIntegration.AudioDevice defaultAudioDevice;
61 // Contains the currently selected audio device.
62 // This device is changed automatically using a certain scheme where e.g.
63 // a wired headset "wins" over speaker phone. It is also possible for a
64 // user to explicitly select a device (and overrid any predefined scheme).
65 // See |userSelectedAudioDevice| for details.
66 private CallIntegration.AudioDevice selectedAudioDevice;
67 // Contains the user-selected audio device which overrides the predefined
68 // selection scheme.
69 // TODO(henrika): always set to AudioDevice.NONE today. Add support for
70 // explicit selection based on choice by userSelectedAudioDevice.
71 private CallIntegration.AudioDevice userSelectedAudioDevice;
72
73 // Contains a list of available audio devices. A Set collection is used to
74 // avoid duplicate elements.
75 private Set<CallIntegration.AudioDevice> audioDevices = new HashSet<>();
76 // Broadcast receiver for wired headset intent broadcasts.
77 private final BroadcastReceiver wiredHeadsetReceiver;
78 // Callback method for changes in audio focus.
79 @Nullable
80 private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
81
82 public AppRTCAudioManager(final Context context) {
83 apprtcContext = context;
84 audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
85 bluetoothManager = AppRTCBluetoothManager.create(context, this);
86 wiredHeadsetReceiver = new WiredHeadsetReceiver();
87 amState = AudioManagerState.UNINITIALIZED;
88 // CallIntegration / Connection uses Earpiece as default too
89 if (hasEarpiece()) {
90 defaultAudioDevice = CallIntegration.AudioDevice.EARPIECE;
91 } else {
92 defaultAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
93 }
94 Log.d(Config.LOGTAG, "defaultAudioDevice: " + defaultAudioDevice);
95 AppRTCUtils.logDeviceInfo(Config.LOGTAG);
96 }
97
98 public static boolean isMicrophoneAvailable() {
99 microphoneLatch = new CountDownLatch(1);
100 AudioRecord audioRecord = null;
101 boolean available = true;
102 try {
103 final int sampleRate = 44100;
104 final int channel = AudioFormat.CHANNEL_IN_MONO;
105 final int format = AudioFormat.ENCODING_PCM_16BIT;
106 final int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, format);
107 audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channel, format, bufferSize);
108 audioRecord.startRecording();
109 final short[] buffer = new short[bufferSize];
110 final int audioStatus = audioRecord.read(buffer, 0, bufferSize);
111 if (audioStatus == AudioRecord.ERROR_INVALID_OPERATION || audioStatus == AudioRecord.STATE_UNINITIALIZED)
112 available = false;
113 } catch (Exception e) {
114 available = false;
115 } finally {
116 release(audioRecord);
117
118 }
119 microphoneLatch.countDown();
120 return available;
121 }
122
123 private static void release(final AudioRecord audioRecord) {
124 if (audioRecord == null) {
125 return;
126 }
127 try {
128 audioRecord.release();
129 } catch (Exception e) {
130 //ignore
131 }
132 }
133
134 @SuppressWarnings("deprecation")
135 public void start(AudioManagerEvents audioManagerEvents) {
136 Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".start()");
137 ThreadUtils.checkIsOnMainThread();
138 if (amState == AudioManagerState.RUNNING) {
139 Log.e(Config.LOGTAG, "AudioManager is already active");
140 return;
141 }
142 awaitMicrophoneLatch();
143 this.audioManagerEvents = audioManagerEvents;
144 amState = AudioManagerState.RUNNING;
145 // Store current audio state so we can restore it when stop() is called.
146 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
147 savedIsMicrophoneMute = audioManager.isMicrophoneMute();
148 hasWiredHeadset = hasWiredHeadset();
149 // Create an AudioManager.OnAudioFocusChangeListener instance.
150 audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
151 // Called on the listener to notify if the audio focus for this listener has been changed.
152 // The |focusChange| value indicates whether the focus was gained, whether the focus was lost,
153 // and whether that loss is transient, or whether the new focus holder will hold it for an
154 // unknown amount of time.
155 // TODO(henrika): possibly extend support of handling audio-focus changes. Only contains
156 // logging for now.
157 @Override
158 public void onAudioFocusChange(int focusChange) {
159 final String typeOfChange;
160 switch (focusChange) {
161 case AudioManager.AUDIOFOCUS_GAIN:
162 typeOfChange = "AUDIOFOCUS_GAIN";
163 break;
164 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
165 typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT";
166 break;
167 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
168 typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE";
169 break;
170 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
171 typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK";
172 break;
173 case AudioManager.AUDIOFOCUS_LOSS:
174 typeOfChange = "AUDIOFOCUS_LOSS";
175 break;
176 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
177 typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT";
178 break;
179 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
180 typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK";
181 break;
182 default:
183 typeOfChange = "AUDIOFOCUS_INVALID";
184 break;
185 }
186 Log.d(Config.LOGTAG, "onAudioFocusChange: " + typeOfChange);
187 }
188 };
189 // Request audio playout focus (without ducking) and install listener for changes in focus.
190 int result = audioManager.requestAudioFocus(audioFocusChangeListener,
191 AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
192 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
193 Log.d(Config.LOGTAG, "Audio focus request granted for VOICE_CALL streams");
194 } else {
195 Log.e(Config.LOGTAG, "Audio focus request failed");
196 }
197 // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
198 // required to be in this mode when playout and/or recording starts for
199 // best possible VoIP performance.
200 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
201 // Always disable microphone mute during a WebRTC call.
202 setMicrophoneMute(false);
203 // Set initial device states.
204 userSelectedAudioDevice = CallIntegration.AudioDevice.NONE;
205 selectedAudioDevice = CallIntegration.AudioDevice.NONE;
206 audioDevices.clear();
207 // Initialize and start Bluetooth if a BT device is available or initiate
208 // detection of new (enabled) BT devices.
209 bluetoothManager.start();
210 // Do initial selection of audio device. This setting can later be changed
211 // either by adding/removing a BT or wired headset or by covering/uncovering
212 // the proximity sensor.
213 updateAudioDeviceState();
214 // Register receiver for broadcast intents related to adding/removing a
215 // wired headset.
216 registerReceiver(wiredHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
217 Log.d(Config.LOGTAG, "AudioManager started");
218 }
219
220 private void awaitMicrophoneLatch() {
221 final CountDownLatch latch = microphoneLatch;
222 if (latch == null) {
223 return;
224 }
225 try {
226 latch.await();
227 } catch (InterruptedException e) {
228 //ignore
229 }
230 }
231
232 @SuppressWarnings("deprecation")
233 public void stop() {
234 Log.d(Config.LOGTAG,"appRtpAudioManager.stop()");
235 Log.d(Config.LOGTAG, AppRTCAudioManager.class.getName() + ".stop()");
236 ThreadUtils.checkIsOnMainThread();
237 if (amState != AudioManagerState.RUNNING) {
238 Log.e(Config.LOGTAG, "Trying to stop AudioManager in incorrect state: " + amState);
239 return;
240 }
241 amState = AudioManagerState.UNINITIALIZED;
242 unregisterReceiver(wiredHeadsetReceiver);
243 bluetoothManager.stop();
244 // Restore previously stored audio states.
245 setSpeakerphoneOn(savedIsSpeakerPhoneOn);
246 setMicrophoneMute(savedIsMicrophoneMute);
247 audioManager.setMode(AudioManager.MODE_NORMAL);
248 // Abandon audio focus. Gives the previous focus owner, if any, focus.
249 audioManager.abandonAudioFocus(audioFocusChangeListener);
250 audioFocusChangeListener = null;
251 audioManagerEvents = null;
252 Log.d(Config.LOGTAG,"appRtpAudioManager.stopped()");
253 }
254
255 /**
256 * Changes selection of the currently active audio device.
257 */
258 private void setAudioDeviceInternal(CallIntegration.AudioDevice device) {
259 Log.d(Config.LOGTAG, "setAudioDeviceInternal(device=" + device + ")");
260 AppRTCUtils.assertIsTrue(audioDevices.contains(device));
261 switch (device) {
262 case SPEAKER_PHONE:
263 setSpeakerphoneOn(true);
264 break;
265 case EARPIECE:
266 case WIRED_HEADSET:
267 case BLUETOOTH:
268 setSpeakerphoneOn(false);
269 break;
270 default:
271 Log.e(Config.LOGTAG, "Invalid audio device selection");
272 break;
273 }
274 selectedAudioDevice = device;
275 }
276
277 /**
278 * Changes default audio device.
279 * TODO(henrika): add usage of this method in the AppRTCMobile client.
280 */
281 public void setDefaultAudioDevice(CallIntegration.AudioDevice defaultDevice) {
282 ThreadUtils.checkIsOnMainThread();
283 switch (defaultDevice) {
284 case SPEAKER_PHONE:
285 defaultAudioDevice = defaultDevice;
286 break;
287 case EARPIECE:
288 if (hasEarpiece()) {
289 defaultAudioDevice = defaultDevice;
290 } else {
291 defaultAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
292 }
293 break;
294 default:
295 Log.e(Config.LOGTAG, "Invalid default audio device selection");
296 break;
297 }
298 Log.d(Config.LOGTAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
299 updateAudioDeviceState();
300 }
301
302 /**
303 * Changes selection of the currently active audio device.
304 */
305 public void selectAudioDevice(CallIntegration.AudioDevice device) {
306 ThreadUtils.checkIsOnMainThread();
307 if (!audioDevices.contains(device)) {
308 Log.e(Config.LOGTAG, "Can not select " + device + " from available " + audioDevices);
309 }
310 userSelectedAudioDevice = device;
311 updateAudioDeviceState();
312 }
313
314 /**
315 * Returns current set of available/selectable audio devices.
316 */
317 public Set<CallIntegration.AudioDevice> getAudioDevices() {
318 ThreadUtils.checkIsOnMainThread();
319 return Collections.unmodifiableSet(new HashSet<>(audioDevices));
320 }
321
322 /**
323 * Returns the currently selected audio device.
324 */
325 public CallIntegration.AudioDevice getSelectedAudioDevice() {
326 return selectedAudioDevice;
327 }
328
329 /**
330 * Helper method for receiver registration.
331 */
332 private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
333 apprtcContext.registerReceiver(receiver, filter);
334 }
335
336 /**
337 * Helper method for unregistration of an existing receiver.
338 */
339 private void unregisterReceiver(BroadcastReceiver receiver) {
340 apprtcContext.unregisterReceiver(receiver);
341 }
342
343 /**
344 * Sets the speaker phone mode.
345 */
346 private void setSpeakerphoneOn(boolean on) {
347 boolean wasOn = audioManager.isSpeakerphoneOn();
348 if (wasOn == on) {
349 return;
350 }
351 audioManager.setSpeakerphoneOn(on);
352 }
353
354 /**
355 * Sets the microphone mute state.
356 */
357 private void setMicrophoneMute(boolean on) {
358 boolean wasMuted = audioManager.isMicrophoneMute();
359 if (wasMuted == on) {
360 return;
361 }
362 audioManager.setMicrophoneMute(on);
363 }
364
365 /**
366 * Gets the current earpiece state.
367 */
368 private boolean hasEarpiece() {
369 return apprtcContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
370 }
371
372 /**
373 * Checks whether a wired headset is connected or not.
374 * This is not a valid indication that audio playback is actually over
375 * the wired headset as audio routing depends on other conditions. We
376 * only use it as an early indicator (during initialization) of an attached
377 * wired headset.
378 */
379 @Deprecated
380 private boolean hasWiredHeadset() {
381 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
382 return audioManager.isWiredHeadsetOn();
383 } else {
384 final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
385 for (AudioDeviceInfo device : devices) {
386 final int type = device.getType();
387 if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
388 Log.d(Config.LOGTAG, "hasWiredHeadset: found wired headset");
389 return true;
390 } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
391 Log.d(Config.LOGTAG, "hasWiredHeadset: found USB audio device");
392 return true;
393 }
394 }
395 return false;
396 }
397 }
398
399 /**
400 * Updates list of possible audio devices and make new device selection.
401 * TODO(henrika): add unit test to verify all state transitions.
402 */
403 public void updateAudioDeviceState() {
404 ThreadUtils.checkIsOnMainThread();
405 Log.d(Config.LOGTAG, "--- updateAudioDeviceState: "
406 + "wired headset=" + hasWiredHeadset + ", "
407 + "BT state=" + bluetoothManager.getState());
408 Log.d(Config.LOGTAG, "Device status: "
409 + "available=" + audioDevices + ", "
410 + "selected=" + selectedAudioDevice + ", "
411 + "user selected=" + userSelectedAudioDevice);
412 // Check if any Bluetooth headset is connected. The internal BT state will
413 // change accordingly.
414 // TODO(henrika): perhaps wrap required state into BT manager.
415 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
416 || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAVAILABLE
417 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_DISCONNECTING) {
418 bluetoothManager.updateDevice();
419 }
420 // Update the set of available audio devices.
421 Set<CallIntegration.AudioDevice> newAudioDevices = new HashSet<>();
422 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
423 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
424 || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) {
425 newAudioDevices.add(CallIntegration.AudioDevice.BLUETOOTH);
426 }
427 if (hasWiredHeadset) {
428 // If a wired headset is connected, then it is the only possible option.
429 newAudioDevices.add(CallIntegration.AudioDevice.WIRED_HEADSET);
430 } else {
431 // No wired headset, hence the audio-device list can contain speaker
432 // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
433 newAudioDevices.add(CallIntegration.AudioDevice.SPEAKER_PHONE);
434 if (hasEarpiece()) {
435 newAudioDevices.add(CallIntegration.AudioDevice.EARPIECE);
436 }
437 }
438 // Store state which is set to true if the device list has changed.
439 boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
440 // Update the existing audio device set.
441 audioDevices = newAudioDevices;
442 // Correct user selected audio devices if needed.
443 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAVAILABLE
444 && userSelectedAudioDevice == CallIntegration.AudioDevice.BLUETOOTH) {
445 // If BT is not available, it can't be the user selection.
446 userSelectedAudioDevice = CallIntegration.AudioDevice.NONE;
447 }
448 if (hasWiredHeadset && userSelectedAudioDevice == CallIntegration.AudioDevice.SPEAKER_PHONE) {
449 // If user selected speaker phone, but then plugged wired headset then make
450 // wired headset as user selected device.
451 userSelectedAudioDevice = CallIntegration.AudioDevice.WIRED_HEADSET;
452 }
453 if (!hasWiredHeadset && userSelectedAudioDevice == CallIntegration.AudioDevice.WIRED_HEADSET) {
454 // If user selected wired headset, but then unplugged wired headset then make
455 // speaker phone as user selected device.
456 userSelectedAudioDevice = CallIntegration.AudioDevice.SPEAKER_PHONE;
457 }
458 // Need to start Bluetooth if it is available and user either selected it explicitly or
459 // user did not select any output device.
460 boolean needBluetoothAudioStart =
461 bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
462 && (userSelectedAudioDevice == CallIntegration.AudioDevice.NONE
463 || userSelectedAudioDevice == CallIntegration.AudioDevice.BLUETOOTH);
464 // Need to stop Bluetooth audio if user selected different device and
465 // Bluetooth SCO connection is established or in the process.
466 boolean needBluetoothAudioStop =
467 (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
468 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING)
469 && (userSelectedAudioDevice != CallIntegration.AudioDevice.NONE
470 && userSelectedAudioDevice != CallIntegration.AudioDevice.BLUETOOTH);
471 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
472 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
473 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) {
474 Log.d(Config.LOGTAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
475 + "stop=" + needBluetoothAudioStop + ", "
476 + "BT state=" + bluetoothManager.getState());
477 }
478 // Start or stop Bluetooth SCO connection given states set earlier.
479 if (needBluetoothAudioStop) {
480 bluetoothManager.stopScoAudio();
481 bluetoothManager.updateDevice();
482 }
483 if (needBluetoothAudioStart && !needBluetoothAudioStop) {
484 // Attempt to start Bluetooth SCO audio (takes a few second to start).
485 if (!bluetoothManager.startScoAudio()) {
486 // Remove BLUETOOTH from list of available devices since SCO failed.
487 audioDevices.remove(CallIntegration.AudioDevice.BLUETOOTH);
488 audioDeviceSetUpdated = true;
489 }
490 }
491 // Update selected audio device.
492 final CallIntegration.AudioDevice newAudioDevice;
493 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) {
494 // If a Bluetooth is connected, then it should be used as output audio
495 // device. Note that it is not sufficient that a headset is available;
496 // an active SCO channel must also be up and running.
497 newAudioDevice = CallIntegration.AudioDevice.BLUETOOTH;
498 } else if (hasWiredHeadset) {
499 // If a wired headset is connected, but Bluetooth is not, then wired headset is used as
500 // audio device.
501 newAudioDevice = CallIntegration.AudioDevice.WIRED_HEADSET;
502 } else {
503 // No wired headset and no Bluetooth, hence the audio-device list can contain speaker
504 // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
505 // |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or AudioDevice.EARPIECE
506 // depending on the user's selection.
507 newAudioDevice = defaultAudioDevice;
508 }
509 // Switch to new device but only if there has been any changes.
510 if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
511 // Do the required device switch.
512 setAudioDeviceInternal(newAudioDevice);
513 Log.d(Config.LOGTAG, "New device status: "
514 + "available=" + audioDevices + ", "
515 + "selected=" + newAudioDevice);
516 if (audioManagerEvents != null) {
517 // Notify a listening client that audio device has been changed.
518 audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices);
519 }
520 }
521 Log.d(Config.LOGTAG, "--- updateAudioDeviceState done");
522 }
523
524 public void executeOnMain(final Runnable runnable) {
525 ContextCompat.getMainExecutor(apprtcContext).execute(runnable);
526 }
527
528 /**
529 * AudioManager state.
530 */
531 public enum AudioManagerState {
532 UNINITIALIZED,
533 PREINITIALIZED,
534 RUNNING,
535 }
536
537 /**
538 * Selected audio device change event.
539 */
540 public interface AudioManagerEvents {
541 // Callback fired once audio device is changed or list of available audio devices changed.
542 void onAudioDeviceChanged(
543 CallIntegration.AudioDevice selectedAudioDevice, Set<CallIntegration.AudioDevice> availableAudioDevices);
544 }
545
546 /* Receiver which handles changes in wired headset availability. */
547 private class WiredHeadsetReceiver extends BroadcastReceiver {
548 private static final int STATE_UNPLUGGED = 0;
549 private static final int STATE_PLUGGED = 1;
550 private static final int HAS_NO_MIC = 0;
551 private static final int HAS_MIC = 1;
552
553 @Override
554 public void onReceive(Context context, Intent intent) {
555 int state = intent.getIntExtra("state", STATE_UNPLUGGED);
556 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
557 String name = intent.getStringExtra("name");
558 Log.d(Config.LOGTAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": "
559 + "a=" + intent.getAction() + ", s="
560 + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m="
561 + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb="
562 + isInitialStickyBroadcast());
563 hasWiredHeadset = (state == STATE_PLUGGED);
564 updateAudioDeviceState();
565 }
566 }
567}