Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. | 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ | 9 */ |
| 10 | 10 |
| 11 package org.appspot.apprtc; | 11 package org.appspot.apprtc; |
| 12 | 12 |
| 13 import org.appspot.apprtc.util.AppRTCUtils; | 13 import org.appspot.apprtc.util.AppRTCUtils; |
| 14 | 14 |
| 15 import android.content.BroadcastReceiver; | 15 import android.content.BroadcastReceiver; |
| 16 import android.content.Context; | 16 import android.content.Context; |
| 17 import android.content.Intent; | 17 import android.content.Intent; |
| 18 import android.content.IntentFilter; | 18 import android.content.IntentFilter; |
| 19 import android.content.SharedPreferences; | 19 import android.content.SharedPreferences; |
| 20 import android.content.pm.PackageManager; | 20 import android.content.pm.PackageManager; |
| 21 import android.media.AudioManager; | 21 import android.media.AudioManager; |
| 22 import android.preference.PreferenceManager; | 22 import android.preference.PreferenceManager; |
| 23 import android.util.Log; | 23 import android.util.Log; |
| 24 | 24 |
| 25 import org.webrtc.ThreadUtils; | |
| 26 | |
| 25 import java.util.Collections; | 27 import java.util.Collections; |
| 26 import java.util.HashSet; | 28 import java.util.HashSet; |
| 29 import java.util.List; | |
| 27 import java.util.Set; | 30 import java.util.Set; |
| 28 | 31 |
| 29 /** | 32 /** |
| 30 * AppRTCAudioManager manages all audio related parts of the AppRTC demo. | 33 * AppRTCAudioManager manages all audio related parts of the AppRTC demo. |
| 31 */ | 34 */ |
| 32 public class AppRTCAudioManager { | 35 public class AppRTCAudioManager { |
| 33 private static final String TAG = "AppRTCAudioManager"; | 36 private static final String TAG = "AppRTCAudioManager"; |
| 34 private static final String SPEAKERPHONE_AUTO = "auto"; | 37 private static final String SPEAKERPHONE_AUTO = "auto"; |
| 35 private static final String SPEAKERPHONE_TRUE = "true"; | 38 private static final String SPEAKERPHONE_TRUE = "true"; |
| 36 private static final String SPEAKERPHONE_FALSE = "false"; | 39 private static final String SPEAKERPHONE_FALSE = "false"; |
| 37 | 40 |
| 38 /** | 41 /** |
| 39 * AudioDevice is the names of possible audio devices that we currently | 42 * AudioDevice is the names of possible audio devices that we currently |
| 40 * support. | 43 * support. |
| 41 */ | 44 */ |
| 42 // TODO(henrika): add support for BLUETOOTH as well. | 45 public enum AudioDevice { SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, N ONE } |
| 43 public enum AudioDevice { | 46 |
| 44 SPEAKER_PHONE, | 47 /** AudioManager state. */ |
| 45 WIRED_HEADSET, | 48 public enum AudioManagerState { |
| 46 EARPIECE, | 49 UNINITIALIZED, |
| 50 PREINITIALIZED, | |
| 51 RUNNING, | |
| 47 } | 52 } |
| 48 | 53 |
| 54 /** Selected audio device change event. */ | |
| 55 public static interface AudioManagerEvents { | |
| 56 // Callback fired once audio device is changed or list of available audio de vices changed. | |
| 57 void onAudioDeviceChanged( | |
| 58 AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices) ; | |
| 59 } | |
| 60 | |
| 61 private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.Thread Checker(); | |
| 62 | |
| 49 private final Context apprtcContext; | 63 private final Context apprtcContext; |
| 50 private final Runnable onStateChangeListener; | |
| 51 private boolean initialized = false; | |
| 52 private AudioManager audioManager; | 64 private AudioManager audioManager; |
| 65 | |
| 66 private AudioManagerEvents audioManagerEvents; | |
| 67 private AudioManagerState amState; | |
| 53 private int savedAudioMode = AudioManager.MODE_INVALID; | 68 private int savedAudioMode = AudioManager.MODE_INVALID; |
| 54 private boolean savedIsSpeakerPhoneOn = false; | 69 private boolean savedIsSpeakerPhoneOn = false; |
| 55 private boolean savedIsMicrophoneMute = false; | 70 private boolean savedIsMicrophoneMute = false; |
| 71 private boolean hasWiredHeadset = false; | |
| 56 | 72 |
| 57 private final AudioDevice defaultAudioDevice; | 73 // Default audio device; speaker phone for video calls or earpiece for audio |
| 74 // only calls. | |
| 75 private AudioDevice defaultAudioDevice; | |
| 76 | |
| 77 // Contains the currently selected audio device. | |
| 78 private AudioDevice selectedAudioDevice; | |
| 79 | |
| 80 // Contains user selected audio device. | |
| 81 // TODO(henrika): always set to AudioDevice.NONE today. Add support for | |
|
magjed_webrtc
2016/12/10 19:34:44
Is this comment correct? |userSelectedAudioDevice|
henrika_webrtc
2016/12/12 14:13:25
Updated the comments. In short, selectedAudioDevic
| |
| 82 // explicit selection based on choice by userSelectedAudioDevice. | |
| 83 private AudioDevice userSelectedAudioDevice; | |
| 58 | 84 |
| 59 // Contains speakerphone setting: auto, true or false | 85 // Contains speakerphone setting: auto, true or false |
| 60 private final String useSpeakerphone; | 86 private final String useSpeakerphone; |
| 61 | 87 |
| 62 // Proximity sensor object. It measures the proximity of an object in cm | 88 // Proximity sensor object. It measures the proximity of an object in cm |
| 63 // relative to the view screen of a device and can therefore be used to | 89 // relative to the view screen of a device and can therefore be used to |
| 64 // assist device switching (close to ear <=> use headset earpiece if | 90 // assist device switching (close to ear <=> use headset earpiece if |
| 65 // available, far from ear <=> use speaker phone). | 91 // available, far from ear <=> use speaker phone). |
| 66 private AppRTCProximitySensor proximitySensor = null; | 92 private AppRTCProximitySensor proximitySensor = null; |
| 67 | 93 |
| 68 // Contains the currently selected audio device. | 94 // Handles all tasks related to Bluetooth headset devices. |
| 69 private AudioDevice selectedAudioDevice; | 95 private final AppRTCBluetoothManager bluetoothManager; |
| 70 | 96 |
| 71 // Contains a list of available audio devices. A Set collection is used to | 97 // Contains a list of available audio devices. A Set collection is used to |
| 72 // avoid duplicate elements. | 98 // avoid duplicate elements. |
| 73 private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>(); | 99 private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>(); |
| 74 | 100 |
| 75 // Broadcast receiver for wired headset intent broadcasts. | 101 // Broadcast receiver for wired headset intent broadcasts. |
| 76 private BroadcastReceiver wiredHeadsetReceiver; | 102 private BroadcastReceiver wiredHeadsetReceiver; |
| 77 | 103 |
| 78 // Callback method for changes in audio focus. | 104 // Callback method for changes in audio focus. |
| 79 private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; | 105 private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; |
| 80 | 106 |
| 81 // This method is called when the proximity sensor reports a state change, | 107 /** |
| 82 // e.g. from "NEAR to FAR" or from "FAR to NEAR". | 108 * This method is called when the proximity sensor reports a state change, |
| 109 * e.g. from "NEAR to FAR" or from "FAR to NEAR". | |
| 110 */ | |
| 83 private void onProximitySensorChangedState() { | 111 private void onProximitySensorChangedState() { |
| 84 if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) { | 112 if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) { |
| 85 return; | 113 return; |
| 86 } | 114 } |
| 87 | 115 |
| 88 // The proximity sensor should only be activated when there are exactly two | 116 // The proximity sensor should only be activated when there are exactly two |
| 89 // available audio devices. | 117 // available audio devices. |
| 90 if (audioDevices.size() == 2 && audioDevices.contains(AppRTCAudioManager.Aud ioDevice.EARPIECE) | 118 if (audioDevices.size() == 2 && audioDevices.contains(AppRTCAudioManager.Aud ioDevice.EARPIECE) |
| 91 && audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) { | 119 && audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) { |
| 92 if (proximitySensor.sensorReportsNearState()) { | 120 if (proximitySensor.sensorReportsNearState()) { |
| 93 // Sensor reports that a "handset is being held up to a person's ear", | 121 // Sensor reports that a "handset is being held up to a person's ear", |
| 94 // or "something is covering the light sensor". | 122 // or "something is covering the light sensor". |
| 95 setAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE); | 123 setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.EARPIECE); |
| 96 } else { | 124 } else { |
| 97 // Sensor reports that a "handset is removed from a person's ear", or | 125 // Sensor reports that a "handset is removed from a person's ear", or |
| 98 // "the light sensor is no longer covered". | 126 // "the light sensor is no longer covered". |
| 99 setAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); | 127 setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); |
| 100 } | 128 } |
| 101 } | 129 } |
| 102 } | 130 } |
| 103 | 131 |
| 104 /** Construction */ | 132 /* Receiver which handles changes in wired headset availability. */ |
| 105 static AppRTCAudioManager create(Context context, Runnable deviceStateChangeLi stener) { | 133 private class WiredHeadsetReceiver extends BroadcastReceiver { |
| 106 return new AppRTCAudioManager(context, deviceStateChangeListener); | 134 private static final int STATE_UNPLUGGED = 0; |
| 135 private static final int STATE_PLUGGED = 1; | |
| 136 private static final int HAS_NO_MIC = 0; | |
| 137 private static final int HAS_MIC = 1; | |
| 138 | |
| 139 @Override | |
| 140 public void onReceive(Context context, Intent intent) { | |
| 141 int state = intent.getIntExtra("state", STATE_UNPLUGGED); | |
| 142 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); | |
| 143 String name = intent.getStringExtra("name"); | |
| 144 Log.d(TAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": " | |
| 145 + "a=" + intent.getAction() + ", s=" | |
| 146 + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" | |
| 147 + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb=" | |
| 148 + isInitialStickyBroadcast()); | |
| 149 hasWiredHeadset = (state == STATE_PLUGGED); | |
| 150 updateAudioDeviceState(); | |
| 151 } | |
| 152 }; | |
| 153 | |
| 154 /** Construction. */ | |
| 155 static AppRTCAudioManager create(Context context) { | |
| 156 return new AppRTCAudioManager(context); | |
| 107 } | 157 } |
| 108 | 158 |
| 109 private AppRTCAudioManager(Context context, Runnable deviceStateChangeListener ) { | 159 private AppRTCAudioManager(Context context) { |
| 160 Log.d(TAG, "ctor"); | |
| 110 apprtcContext = context; | 161 apprtcContext = context; |
| 111 onStateChangeListener = deviceStateChangeListener; | |
| 112 audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVIC E)); | 162 audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVIC E)); |
| 163 bluetoothManager = AppRTCBluetoothManager.create(context, this); | |
| 164 wiredHeadsetReceiver = new WiredHeadsetReceiver(); | |
| 165 amState = AudioManagerState.UNINITIALIZED; | |
| 113 | 166 |
| 114 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPref erences(context); | 167 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPref erences(context); |
| 115 useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pre f_speakerphone_key), | 168 useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pre f_speakerphone_key), |
| 116 context.getString(R.string.pref_speakerphone_default)); | 169 context.getString(R.string.pref_speakerphone_default)); |
| 117 | 170 Log.d(TAG, "useSpeakerphone: " + useSpeakerphone); |
| 118 if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { | 171 if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { |
| 119 defaultAudioDevice = AudioDevice.EARPIECE; | 172 defaultAudioDevice = AudioDevice.EARPIECE; |
| 120 } else { | 173 } else { |
| 121 defaultAudioDevice = AudioDevice.SPEAKER_PHONE; | 174 defaultAudioDevice = AudioDevice.SPEAKER_PHONE; |
| 122 } | 175 } |
| 123 | 176 |
| 124 // Create and initialize the proximity sensor. | 177 // Create and initialize the proximity sensor. |
| 125 // Tablet devices (e.g. Nexus 7) does not support proximity sensors. | 178 // Tablet devices (e.g. Nexus 7) does not support proximity sensors. |
| 126 // Note that, the sensor will not be active until start() has been called. | 179 // Note that, the sensor will not be active until start() has been called. |
| 127 proximitySensor = AppRTCProximitySensor.create(context, new Runnable() { | 180 proximitySensor = AppRTCProximitySensor.create(context, new Runnable() { |
| 128 // This method will be called each time a state change is detected. | 181 // This method will be called each time a state change is detected. |
| 129 // Example: user holds his hand over the device (closer than ~5 cm), | 182 // Example: user holds his hand over the device (closer than ~5 cm), |
| 130 // or removes his hand from the device. | 183 // or removes his hand from the device. |
| 131 public void run() { | 184 public void run() { |
| 132 onProximitySensorChangedState(); | 185 onProximitySensorChangedState(); |
| 133 } | 186 } |
| 134 }); | 187 }); |
| 188 | |
| 189 Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice); | |
| 135 AppRTCUtils.logDeviceInfo(TAG); | 190 AppRTCUtils.logDeviceInfo(TAG); |
| 136 } | 191 } |
| 137 | 192 |
| 138 public void init() { | 193 public void start(AudioManagerEvents audioManagerEvents) { |
| 139 Log.d(TAG, "init"); | 194 Log.d(TAG, "start"); |
| 140 if (initialized) { | 195 if (amState == AudioManagerState.RUNNING) { |
| 196 Log.e(TAG, "AudioManager is already active"); | |
| 141 return; | 197 return; |
| 142 } | 198 } |
| 199 // TODO(henrika): perhaps call new method called preInitAudio() here if UNIN ITIALIZED. | |
| 143 | 200 |
| 144 // Store current audio state so we can restore it when close() is called. | 201 Log.d(TAG, "AudioManager starts..."); |
| 202 this.audioManagerEvents = audioManagerEvents; | |
| 203 amState = AudioManagerState.RUNNING; | |
| 204 | |
| 205 // Store current audio state so we can restore it when stop() is called. | |
| 145 savedAudioMode = audioManager.getMode(); | 206 savedAudioMode = audioManager.getMode(); |
| 146 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); | 207 savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); |
| 147 savedIsMicrophoneMute = audioManager.isMicrophoneMute(); | 208 savedIsMicrophoneMute = audioManager.isMicrophoneMute(); |
| 209 hasWiredHeadset = hasWiredHeadset(); | |
| 148 | 210 |
| 149 // Create an AudioManager.OnAudioFocusChangeListener instance. | 211 // Create an AudioManager.OnAudioFocusChangeListener instance. |
| 150 audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { | 212 audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { |
| 151 // Called on the listener to notify if the audio focus for this listener h as been changed. | 213 // Called on the listener to notify if the audio focus for this listener h as been changed. |
| 152 // The |focusChange| value indicates whether the focus was gained, whether the focus was lost, | 214 // 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 wil l hold it for an | 215 // and whether that loss is transient, or whether the new focus holder wil l hold it for an |
| 154 // unknown amount of time. | 216 // unknown amount of time. |
| 155 // TODO(henrika): possibly extend support of handling audio-focus changes. Only contains | 217 // TODO(henrika): possibly extend support of handling audio-focus changes. Only contains |
| 156 // logging for now. | 218 // logging for now. |
| 157 @Override | 219 @Override |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 192 AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); | 254 AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); |
| 193 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { | 255 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { |
| 194 Log.d(TAG, "Audio focus request granted for VOICE_CALL streams"); | 256 Log.d(TAG, "Audio focus request granted for VOICE_CALL streams"); |
| 195 } else { | 257 } else { |
| 196 Log.e(TAG, "Audio focus request failed"); | 258 Log.e(TAG, "Audio focus request failed"); |
| 197 } | 259 } |
| 198 | 260 |
| 199 // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is | 261 // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is |
| 200 // required to be in this mode when playout and/or recording starts for | 262 // required to be in this mode when playout and/or recording starts for |
| 201 // best possible VoIP performance. | 263 // best possible VoIP performance. |
| 202 // TODO(henrika): we migh want to start with RINGTONE mode here instead. | |
| 203 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); | 264 audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); |
| 204 | 265 |
| 205 // Always disable microphone mute during a WebRTC call. | 266 // Always disable microphone mute during a WebRTC call. |
| 206 setMicrophoneMute(false); | 267 setMicrophoneMute(false); |
| 207 | 268 |
| 269 // Set initial device states. | |
| 270 userSelectedAudioDevice = AudioDevice.NONE; | |
| 271 selectedAudioDevice = AudioDevice.NONE; | |
| 272 audioDevices.clear(); | |
| 273 | |
| 274 // Initialize and start Bluetooth if a BT device is available or initiate | |
| 275 // detection of new (enabled) BT devices. | |
| 276 bluetoothManager.start(); | |
| 277 | |
| 208 // Do initial selection of audio device. This setting can later be changed | 278 // Do initial selection of audio device. This setting can later be changed |
| 209 // either by adding/removing a wired headset or by covering/uncovering the | 279 // either by adding/removing a BT or wired headset or by covering/uncovering |
| 210 // proximity sensor. | 280 // the proximity sensor. |
| 211 updateAudioDeviceState(hasWiredHeadset()); | 281 updateAudioDeviceState(); |
| 212 | 282 |
| 213 // Register receiver for broadcast intents related to adding/removing a | 283 // Register receiver for broadcast intents related to adding/removing a |
| 214 // wired headset (Intent.ACTION_HEADSET_PLUG). | 284 // wired headset. |
| 215 registerForWiredHeadsetIntentBroadcast(); | 285 registerReceiver(wiredHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSE T_PLUG)); |
| 216 | 286 Log.d(TAG, "AudioManager started"); |
| 217 initialized = true; | |
| 218 } | 287 } |
| 219 | 288 |
| 220 public void close() { | 289 public void stop() { |
| 221 Log.d(TAG, "close"); | 290 Log.d(TAG, "stop"); |
| 222 if (!initialized) { | 291 if (amState != AudioManagerState.RUNNING) { |
| 292 Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState); | |
| 223 return; | 293 return; |
| 224 } | 294 } |
| 295 amState = AudioManagerState.UNINITIALIZED; | |
| 225 | 296 |
| 226 unregisterForWiredHeadsetIntentBroadcast(); | 297 unregisterReceiver(wiredHeadsetReceiver); |
| 298 | |
| 299 bluetoothManager.stop(); | |
| 227 | 300 |
| 228 // Restore previously stored audio states. | 301 // Restore previously stored audio states. |
| 229 setSpeakerphoneOn(savedIsSpeakerPhoneOn); | 302 setSpeakerphoneOn(savedIsSpeakerPhoneOn); |
| 230 setMicrophoneMute(savedIsMicrophoneMute); | 303 setMicrophoneMute(savedIsMicrophoneMute); |
| 231 audioManager.setMode(savedAudioMode); | 304 audioManager.setMode(savedAudioMode); |
| 232 | 305 |
| 233 // Abandon audio focus. Gives the previous focus owner, if any, focus. | 306 // Abandon audio focus. Gives the previous focus owner, if any, focus. |
| 234 audioManager.abandonAudioFocus(audioFocusChangeListener); | 307 audioManager.abandonAudioFocus(audioFocusChangeListener); |
| 235 audioFocusChangeListener = null; | 308 audioFocusChangeListener = null; |
| 236 Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams"); | 309 Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams"); |
| 237 | 310 |
| 238 if (proximitySensor != null) { | 311 if (proximitySensor != null) { |
| 239 proximitySensor.stop(); | 312 proximitySensor.stop(); |
| 240 proximitySensor = null; | 313 proximitySensor = null; |
| 241 } | 314 } |
| 242 | 315 |
| 243 initialized = false; | 316 audioManagerEvents = null; |
| 317 Log.d(TAG, "AudioManager stopped"); | |
| 244 } | 318 } |
| 245 | 319 |
| 246 /** Changes selection of the currently active audio device. */ | 320 /** Changes selection of the currently active audio device. */ |
| 247 public void setAudioDevice(AudioDevice device) { | 321 private void setAudioDeviceInternal(AudioDevice device) { |
| 248 Log.d(TAG, "setAudioDevice(device=" + device + ")"); | 322 Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")"); |
| 249 AppRTCUtils.assertIsTrue(audioDevices.contains(device)); | 323 AppRTCUtils.assertIsTrue(audioDevices.contains(device)); |
| 250 | 324 |
| 251 switch (device) { | 325 switch (device) { |
| 252 case SPEAKER_PHONE: | 326 case SPEAKER_PHONE: |
| 253 setSpeakerphoneOn(true); | 327 setSpeakerphoneOn(true); |
| 254 selectedAudioDevice = AudioDevice.SPEAKER_PHONE; | |
| 255 break; | 328 break; |
| 256 case EARPIECE: | 329 case EARPIECE: |
| 257 setSpeakerphoneOn(false); | 330 setSpeakerphoneOn(false); |
| 258 selectedAudioDevice = AudioDevice.EARPIECE; | |
| 259 break; | 331 break; |
| 260 case WIRED_HEADSET: | 332 case WIRED_HEADSET: |
| 261 setSpeakerphoneOn(false); | 333 setSpeakerphoneOn(false); |
| 262 selectedAudioDevice = AudioDevice.WIRED_HEADSET; | 334 break; |
| 335 case BLUETOOTH: | |
| 336 setSpeakerphoneOn(false); | |
| 263 break; | 337 break; |
| 264 default: | 338 default: |
| 265 Log.e(TAG, "Invalid audio device selection"); | 339 Log.e(TAG, "Invalid audio device selection"); |
| 266 break; | 340 break; |
| 267 } | 341 } |
| 268 onAudioManagerChangedState(); | 342 selectedAudioDevice = device; |
| 343 } | |
| 344 | |
| 345 /** | |
| 346 * Changes default audio device. | |
| 347 * TODO(henrika): add usage of this method in the AppRTCMobile client. | |
| 348 */ | |
| 349 public synchronized void setDefaultAudioDevice(AudioDevice defaultDevice) { | |
| 350 switch (defaultDevice) { | |
| 351 case SPEAKER_PHONE: | |
| 352 defaultAudioDevice = defaultDevice; | |
| 353 break; | |
| 354 case EARPIECE: | |
| 355 if (hasEarpiece()) { | |
| 356 defaultAudioDevice = defaultDevice; | |
| 357 } else { | |
| 358 defaultAudioDevice = AudioDevice.SPEAKER_PHONE; | |
| 359 } | |
| 360 break; | |
| 361 default: | |
| 362 Log.e(TAG, "Invalid default audio device selection"); | |
| 363 break; | |
| 364 } | |
| 365 Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")"); | |
| 366 updateAudioDeviceState(); | |
| 367 } | |
| 368 | |
| 369 /** Changes selection of the currently active audio device. */ | |
| 370 public synchronized void selectAudioDevice(AudioDevice device) { | |
| 371 if (!audioDevices.contains(device)) { | |
| 372 Log.e(TAG, "Can not select " + device + " from available " + audioDevices) ; | |
| 373 } | |
| 374 userSelectedAudioDevice = device; | |
| 375 updateAudioDeviceState(); | |
| 269 } | 376 } |
| 270 | 377 |
| 271 /** Returns current set of available/selectable audio devices. */ | 378 /** Returns current set of available/selectable audio devices. */ |
| 272 public Set<AudioDevice> getAudioDevices() { | 379 public synchronized Set<AudioDevice> getAudioDevices() { |
|
magjed_webrtc
2016/12/10 19:34:44
These functions that you have marked synchronized,
henrika_webrtc
2016/12/12 14:13:25
Done.
| |
| 273 return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); | 380 return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); |
| 274 } | 381 } |
| 275 | 382 |
| 276 /** Returns the currently selected audio device. */ | 383 /** Returns the currently selected audio device. */ |
| 277 public AudioDevice getSelectedAudioDevice() { | 384 public synchronized AudioDevice getSelectedAudioDevice() { |
| 278 return selectedAudioDevice; | 385 return selectedAudioDevice; |
| 279 } | 386 } |
| 280 | 387 |
| 281 /** | 388 /** Helper method for receiver registration. */ |
| 282 * Registers receiver for the broadcasted intent when a wired headset is | 389 private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { |
| 283 * plugged in or unplugged. The received intent will have an extra | 390 apprtcContext.registerReceiver(receiver, filter); |
| 284 * 'state' value where 0 means unplugged, and 1 means plugged. | |
| 285 */ | |
| 286 private void registerForWiredHeadsetIntentBroadcast() { | |
| 287 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); | |
| 288 | |
| 289 /** Receiver which handles changes in wired headset availability. */ | |
| 290 wiredHeadsetReceiver = new BroadcastReceiver() { | |
| 291 private static final int STATE_UNPLUGGED = 0; | |
| 292 private static final int STATE_PLUGGED = 1; | |
| 293 private static final int HAS_NO_MIC = 0; | |
| 294 private static final int HAS_MIC = 1; | |
| 295 | |
| 296 @Override | |
| 297 public void onReceive(Context context, Intent intent) { | |
| 298 int state = intent.getIntExtra("state", STATE_UNPLUGGED); | |
| 299 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); | |
| 300 String name = intent.getStringExtra("name"); | |
| 301 Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": " | |
| 302 + "a=" + intent.getAction() + ", s=" | |
| 303 + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" | |
| 304 + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + " , sb=" | |
| 305 + isInitialStickyBroadcast()); | |
| 306 | |
| 307 boolean hasWiredHeadset = (state == STATE_PLUGGED); | |
| 308 switch (state) { | |
| 309 case STATE_UNPLUGGED: | |
| 310 updateAudioDeviceState(hasWiredHeadset); | |
| 311 break; | |
| 312 case STATE_PLUGGED: | |
| 313 if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) { | |
| 314 updateAudioDeviceState(hasWiredHeadset); | |
| 315 } | |
| 316 break; | |
| 317 default: | |
| 318 Log.e(TAG, "Invalid state"); | |
| 319 break; | |
| 320 } | |
| 321 } | |
| 322 }; | |
| 323 | |
| 324 apprtcContext.registerReceiver(wiredHeadsetReceiver, filter); | |
| 325 } | 391 } |
| 326 | 392 |
| 327 /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ | 393 /** Helper method for unregistration of an existing receiver. */ |
| 328 private void unregisterForWiredHeadsetIntentBroadcast() { | 394 private void unregisterReceiver(BroadcastReceiver receiver) { |
| 329 apprtcContext.unregisterReceiver(wiredHeadsetReceiver); | 395 apprtcContext.unregisterReceiver(receiver); |
| 330 wiredHeadsetReceiver = null; | |
| 331 } | 396 } |
| 332 | 397 |
| 333 /** Sets the speaker phone mode. */ | 398 /** Sets the speaker phone mode. */ |
| 334 private void setSpeakerphoneOn(boolean on) { | 399 private void setSpeakerphoneOn(boolean on) { |
| 335 boolean wasOn = audioManager.isSpeakerphoneOn(); | 400 boolean wasOn = audioManager.isSpeakerphoneOn(); |
| 336 if (wasOn == on) { | 401 if (wasOn == on) { |
| 337 return; | 402 return; |
| 338 } | 403 } |
| 339 audioManager.setSpeakerphoneOn(on); | 404 audioManager.setSpeakerphoneOn(on); |
| 340 } | 405 } |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 358 * This is not a valid indication that audio playback is actually over | 423 * This is not a valid indication that audio playback is actually over |
| 359 * the wired headset as audio routing depends on other conditions. We | 424 * the wired headset as audio routing depends on other conditions. We |
| 360 * only use it as an early indicator (during initialization) of an attached | 425 * only use it as an early indicator (during initialization) of an attached |
| 361 * wired headset. | 426 * wired headset. |
| 362 */ | 427 */ |
| 363 @Deprecated | 428 @Deprecated |
| 364 private boolean hasWiredHeadset() { | 429 private boolean hasWiredHeadset() { |
| 365 return audioManager.isWiredHeadsetOn(); | 430 return audioManager.isWiredHeadsetOn(); |
| 366 } | 431 } |
| 367 | 432 |
| 368 /** Update list of possible audio devices and make new device selection. */ | 433 /** Updates list of possible audio devices and make new device selection. */ |
| 369 private void updateAudioDeviceState(boolean hasWiredHeadset) { | 434 public void updateAudioDeviceState() { |
|
magjed_webrtc
2016/12/10 19:34:44
It would be nice with tests for this function as w
henrika_webrtc
2016/12/12 14:13:25
I got the same idea when writing the other test. A
| |
| 370 // Update the list of available audio devices. | 435 threadChecker.checkIsOnValidThread(); |
| 371 audioDevices.clear(); | 436 Log.d(TAG, "--- updateAudioDeviceState: " |
| 437 + "wired headset=" + hasWiredHeadset + ", " | |
| 438 + "BT state=" + bluetoothManager.getState()); | |
| 439 Log.d(TAG, "Device status: " | |
| 440 + "available=" + audioDevices + ", " | |
| 441 + "selected=" + selectedAudioDevice + ", " | |
| 442 + "user selected=" + userSelectedAudioDevice); | |
| 443 | |
| 444 // Check if any Bluetooth headset is connected. The internal BT state will | |
| 445 // change accordingly. | |
| 446 // TODO(henrika): perhaps wrap required state into BT manager. | |
| 447 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAI LABLE | |
| 448 || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_U NAVAILABLE | |
| 449 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_DISCO NNECTING) { | |
| 450 bluetoothManager.updateDevice(); | |
| 451 } | |
| 452 | |
| 453 // Update the set of available audio devices. | |
| 454 Set<AudioDevice> newAudioDevices = new HashSet<>(); | |
| 455 | |
| 456 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTE D | |
| 457 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNE CTING | |
| 458 || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_A VAILABLE) { | |
| 459 newAudioDevices.add(AudioDevice.BLUETOOTH); | |
| 460 } | |
| 461 | |
| 372 if (hasWiredHeadset) { | 462 if (hasWiredHeadset) { |
| 373 // If a wired headset is connected, then it is the only possible option. | 463 // If a wired headset is connected, then it is the only possible option. |
| 374 audioDevices.add(AudioDevice.WIRED_HEADSET); | 464 newAudioDevices.add(AudioDevice.WIRED_HEADSET); |
| 375 } else { | 465 } else { |
| 376 // No wired headset, hence the audio-device list can contain speaker | 466 // No wired headset, hence the audio-device list can contain speaker |
| 377 // phone (on a tablet), or speaker phone and earpiece (on mobile phone). | 467 // phone (on a tablet), or speaker phone and earpiece (on mobile phone). |
| 378 audioDevices.add(AudioDevice.SPEAKER_PHONE); | 468 newAudioDevices.add(AudioDevice.SPEAKER_PHONE); |
| 379 if (hasEarpiece()) { | 469 if (hasEarpiece()) { |
| 380 audioDevices.add(AudioDevice.EARPIECE); | 470 newAudioDevices.add(AudioDevice.EARPIECE); |
| 381 } | 471 } |
| 382 } | 472 } |
| 383 Log.d(TAG, "audioDevices: " + audioDevices); | |
| 384 | 473 |
| 385 // Switch to correct audio device given the list of available audio devices. | 474 // Update the existing audio device set if needed. |
| 386 if (hasWiredHeadset) { | 475 boolean audioDeviceSetUpdated = |
|
magjed_webrtc
2016/12/10 19:34:44
Is it possible to write it like this instead:
bool
henrika_webrtc
2016/12/12 14:13:25
Yep, it works ;-)
| |
| 387 setAudioDevice(AudioDevice.WIRED_HEADSET); | 476 !audioDevices.containsAll(newAudioDevices) || !newAudioDevices.containsA ll(audioDevices); |
| 388 } else { | 477 if (audioDeviceSetUpdated) { |
| 389 setAudioDevice(defaultAudioDevice); | 478 audioDevices.clear(); |
| 390 } | 479 audioDevices.addAll(newAudioDevices); |
| 391 } | |
| 392 | |
| 393 /** Called each time a new audio device has been added or removed. */ | |
| 394 private void onAudioManagerChangedState() { | |
| 395 Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices + ", select ed=" | |
| 396 + selectedAudioDevice); | |
| 397 | |
| 398 // Enable the proximity sensor if there are two available audio devices | |
| 399 // in the list. Given the current implementation, we know that the choice | |
| 400 // will then be between EARPIECE and SPEAKER_PHONE. | |
| 401 if (audioDevices.size() == 2) { | |
| 402 AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE) | |
| 403 && audioDevices.contains(AudioDevice.SPEAKER_PHONE)); | |
| 404 // Start the proximity sensor. | |
| 405 proximitySensor.start(); | |
| 406 } else if (audioDevices.size() == 1) { | |
| 407 // Stop the proximity sensor since it is no longer needed. | |
| 408 proximitySensor.stop(); | |
| 409 } else { | |
| 410 Log.e(TAG, "Invalid device list"); | |
| 411 } | 480 } |
| 412 | 481 |
| 413 if (onStateChangeListener != null) { | 482 // Correct user selected audio devices if needed. |
| 414 // Run callback to notify a listening client. The client can then | 483 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAV AILABLE |
| 415 // use public getters to query the new state. | 484 && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { |
| 416 onStateChangeListener.run(); | 485 // If BT is not available, it can't be the user selection. |
| 486 userSelectedAudioDevice = AudioDevice.NONE; | |
| 417 } | 487 } |
| 488 if (hasWiredHeadset && userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE) { | |
| 489 // If user selected speaker phone, but then plugged wired headset then mak e | |
| 490 // wired headset as user selected device. | |
| 491 userSelectedAudioDevice = AudioDevice.WIRED_HEADSET; | |
| 492 } | |
| 493 if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET ) { | |
| 494 // If user selected wired headset, but then unplugged wired headset then m ake | |
| 495 // speaker phone as user selected device. | |
| 496 userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE; | |
| 497 } | |
| 498 | |
| 499 // Need to start Bluetooth if it is available and user either selected it ex plicitly or | |
| 500 // user did not select any output device. | |
| 501 boolean needBluetoothAudioStart = | |
| 502 bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAI LABLE | |
| 503 && (userSelectedAudioDevice == AudioDevice.NONE | |
| 504 || userSelectedAudioDevice == AudioDevice.BLUETOOTH); | |
| 505 | |
| 506 // Need to stop Bluetooth audio if user selected different device and | |
| 507 // Bluetooth SCO connection is established or in the process. | |
| 508 boolean needBluetoothAudioStop = | |
| 509 (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECT ED | |
| 510 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_C ONNECTING) | |
| 511 && (userSelectedAudioDevice != AudioDevice.NONE | |
| 512 && userSelectedAudioDevice != AudioDevice.BLUETOOTH); | |
| 513 | |
| 514 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAI LABLE | |
| 515 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNE CTING | |
| 516 || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNE CTED) { | |
| 517 Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", " | |
| 518 + "stop=" + needBluetoothAudioStop + ", " | |
| 519 + "BT state=" + bluetoothManager.getState()); | |
| 520 } | |
| 521 | |
| 522 // Start or stop Bluetooth SCO connection given states set earlier. | |
| 523 if (needBluetoothAudioStop) { | |
| 524 bluetoothManager.stopScoAudio(); | |
| 525 bluetoothManager.updateDevice(); | |
| 526 } | |
| 527 | |
| 528 if (needBluetoothAudioStart && !needBluetoothAudioStop) { | |
| 529 // Attempt to start Bluetooth SCO audio (takes a few second to start). | |
| 530 if (!bluetoothManager.startScoAudio()) { | |
| 531 // Remove BLUETOOTH from list of available devices since SCO failed. | |
| 532 audioDevices.remove(AudioDevice.BLUETOOTH); | |
| 533 audioDeviceSetUpdated = true; | |
| 534 } | |
| 535 } | |
| 536 | |
| 537 // Update selected audio device. | |
| 538 AudioDevice newAudioDevice = selectedAudioDevice; | |
| 539 | |
| 540 if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTE D) { | |
| 541 // If a Bluetooth is connected, then it should be used as output audio | |
| 542 // device. Note that it is not sufficient that a headset is available; | |
| 543 // an active SCO channel must also be up and running. | |
| 544 newAudioDevice = AudioDevice.BLUETOOTH; | |
| 545 } else if (hasWiredHeadset) { | |
| 546 // If a wired headset is connected, but Bluetooth is not, then wired heads et is used as | |
| 547 // audio device. | |
| 548 newAudioDevice = AudioDevice.WIRED_HEADSET; | |
| 549 } else { | |
| 550 // No wired headset and no Bluetooth, hence the audio-device list can cont ain speaker | |
| 551 // phone (on a tablet), or speaker phone and earpiece (on mobile phone). | |
| 552 // |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or Audio Device.EARPIECE | |
| 553 // depending on the user's selection. | |
| 554 newAudioDevice = defaultAudioDevice; | |
| 555 } | |
| 556 // Switch to new device but only if there has been any changes. | |
| 557 if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) { | |
| 558 // Do the required device switch. | |
| 559 setAudioDeviceInternal(newAudioDevice); | |
| 560 Log.d(TAG, "New device status: " | |
| 561 + "available=" + audioDevices + ", " | |
| 562 + "selected=" + newAudioDevice); | |
| 563 if (audioManagerEvents != null) { | |
| 564 // Notify a listening client that audio device has been changed. | |
| 565 audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevice s); | |
| 566 } | |
| 567 } | |
| 568 Log.d(TAG, "--- updateAudioDeviceState done"); | |
| 418 } | 569 } |
| 419 } | 570 } |
| OLD | NEW |