Index: webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
index 6e66b08395ca78e26a72887ce831ab6c4cf232d3..8ca0333b14d1e54c64daa29060f4865d57b5c128 100644 |
--- a/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
+++ b/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
@@ -12,6 +12,10 @@ package org.appspot.apprtc; |
import org.appspot.apprtc.util.AppRTCUtils; |
+import android.bluetooth.BluetoothAdapter; |
+import android.bluetooth.BluetoothDevice; |
+import android.bluetooth.BluetoothHeadset; |
+import android.bluetooth.BluetoothProfile; |
import android.content.BroadcastReceiver; |
import android.content.Context; |
import android.content.Intent; |
@@ -19,54 +23,107 @@ import android.content.IntentFilter; |
import android.content.SharedPreferences; |
import android.content.pm.PackageManager; |
import android.media.AudioManager; |
+import android.os.Handler; |
+import android.os.HandlerThread; |
+import android.os.Process; |
import android.preference.PreferenceManager; |
import android.util.Log; |
import java.util.Collections; |
import java.util.HashSet; |
+import java.util.List; |
import java.util.Set; |
-/** |
- * AppRTCAudioManager manages all audio related parts of the AppRTC demo. |
- */ |
+// AppRTCAudioManager manages all audio related parts of the AppRTC demo. |
magjed_webrtc
2016/11/28 12:59:45
We are not following the style guide strictly when
henrika_webrtc
2016/11/28 14:06:18
Acknowledged.
|
public class AppRTCAudioManager { |
private static final String TAG = "AppRTCAudioManager"; |
+ private static final String TAG_BT = "AppRTCAudioManagerBT"; |
private static final String SPEAKERPHONE_AUTO = "auto"; |
private static final String SPEAKERPHONE_TRUE = "true"; |
private static final String SPEAKERPHONE_FALSE = "false"; |
- /** |
- * AudioDevice is the names of possible audio devices that we currently |
- * support. |
- */ |
- // TODO(henrika): add support for BLUETOOTH as well. |
- public enum AudioDevice { |
- SPEAKER_PHONE, |
- WIRED_HEADSET, |
- EARPIECE, |
+ // Timeout interval for starting or stopping audio to a Bluetooth SCO device. |
+ private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000; |
+ // Maximum number of SCO connection attempts. |
+ private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2; |
+ |
+ // AudioDevice is the names of possible audio devices that we currently |
+ // support. |
+ public enum AudioDevice { SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE } |
+ |
+ // AudioManager state. |
+ public enum AudioManagerState { |
+ UNINITIALIZED, |
+ PREINITIALIZED, |
+ RUNNING, |
+ } |
+ |
+ // Bluetooth connection state. |
+ public enum BluetoothState { |
+ // Bluetooth is not available; no adapter or Bluetooth is off. |
+ UNINITIALIZED, |
+ // Bluetooth error happened when trying to start Bluetooth. |
+ ERROR, |
+ // Bluetooth SCO proxy is connected, but no connected Bluetooth headset devices, |
+ // SCO is not started or disconnected. |
+ HEADSET_UNAVAILABLE, |
+ // Bluetooth SCO proxy is connected, connected Bluetooth headset present, but SCO is |
+ // not started or disconnected. |
+ HEADSET_AVAILABLE, |
+ // Bluetooth audio SCO connection with remote device is closing. |
+ SCO_DISCONNECTING, |
+ // Bluetooth audio SCO connection with remote device is initiated. |
+ SCO_CONNECTING, |
+ // Bluetooth audio SCO connection with remote device is established. |
+ SCO_CONNECTED |
+ } |
+ |
+ // Selected audio device change event. |
+ public static interface AudioManagerEvents { |
+ // Callback fired once audio device is changed or list of available audio devices changed. |
+ void onAudioDeviceChanged( |
+ AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices); |
} |
private final Context apprtcContext; |
- private final Runnable onStateChangeListener; |
- private boolean initialized = false; |
private AudioManager audioManager; |
+ private final Handler handler; |
+ |
+ private AudioManagerEvents audioManagerEvents; |
+ private AudioManagerState amState; |
private int savedAudioMode = AudioManager.MODE_INVALID; |
private boolean savedIsSpeakerPhoneOn = false; |
private boolean savedIsMicrophoneMute = false; |
+ private boolean hasWiredHeadset = false; |
- private final AudioDevice defaultAudioDevice; |
+ // Default audio device; speaker phone for video calls or earpiece for audio |
+ // only calls. |
+ private AudioDevice defaultAudioDevice; |
+ |
+ // Contains the currently selected audio device. |
+ private AudioDevice selectedAudioDevice; |
+ |
+ // Contains user selected audio device. |
+ // TODO(henrika): always set to AudioDevice.NONE today. Add support for |
+ // explicit selection based on choice by userSelectedAudioDevice. |
+ private AudioDevice userSelectedAudioDevice; |
// Contains speakerphone setting: auto, true or false |
private final String useSpeakerphone; |
- // 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 AppRTCProximitySensor proximitySensor = null; |
+ // Enabled during initialization if BLUETOOTH permission is granted. |
+ private boolean hasBluetoothPermission = false; |
magjed_webrtc
2016/11/28 12:59:45
I don't think it's a good idea to cache permission
henrika_webrtc
2016/11/28 14:06:18
Acknowledged.
|
- // Contains the currently selected audio device. |
- private AudioDevice selectedAudioDevice; |
+ // Listener for when we have been connected or disconnected to the Headset |
+ // Bluetooth profile. |
+ private final BluetoothProfile.ServiceListener bluetoothServiceListener; |
+ |
+ private BluetoothState bluetoothState; |
+ private BluetoothAdapter bluetoothAdapter; |
+ private BluetoothHeadset bluetoothHeadset; |
+ private BluetoothDevice bluetoothDevice; |
+ // Number of Bluetooth SCO connection attempts. |
+ int scoConnectionAttempts; |
// Contains a list of available audio devices. A Set collection is used to |
// avoid duplicate elements. |
@@ -75,76 +132,198 @@ public class AppRTCAudioManager { |
// Broadcast receiver for wired headset intent broadcasts. |
private BroadcastReceiver wiredHeadsetReceiver; |
+ // Broadcast receiver for Bluetooth headset intent broadcasts. Detects |
+ // headset changes and Bluetooth SCO state changes. |
+ private final BroadcastReceiver bluetoothHeadsetReceiver; |
+ |
// Callback method for changes in audio focus. |
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; |
- // 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)) { |
- return; |
+ // Runs when the Bluetooth timeout expires. We use that timeout after calling |
+ // startBluetoothSco() or stopBluetoothSco() because we're not guaranteed to |
+ // get a callback after those calls. |
+ private final Runnable bluetoothTimeoutRunnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ bluetoothTimeout(); |
+ } |
+ }; |
+ |
+ // Receiver which handles changes in wired headset availability. |
+ private class WiredHeadsetReceiver extends BroadcastReceiver { |
+ private static final int STATE_UNPLUGGED = 0; |
+ private static final int STATE_PLUGGED = 1; |
+ private static final int HAS_NO_MIC = 0; |
+ private static final int HAS_MIC = 1; |
+ |
+ @Override |
+ public void onReceive(Context context, Intent intent) { |
+ int state = intent.getIntExtra("state", STATE_UNPLUGGED); |
+ int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); |
+ String name = intent.getStringExtra("name"); |
+ Log.d(TAG, "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(); |
+ } |
+ }; |
+ |
+ // Intent broadcast receiver which handles changes in Bluetooth device availability. |
+ private class BluetoothHeadsetBroadcastReceiver extends BroadcastReceiver { |
+ @Override |
+ public void onReceive(Context context, Intent intent) { |
+ if (amState != AudioManagerState.RUNNING || bluetoothState == BluetoothState.UNINITIALIZED) { |
+ return; |
+ } |
+ final String action = intent.getAction(); |
+ // Change in connection state of the Headset profile. Note that the |
+ // change does not tell us anything about whether we're streaming |
+ // audio to BT over SCO. |
+ if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { |
+ final int state = |
+ intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED); |
+ final BluetoothDevice device = |
+ (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
+ Log.d(TAG_BT, "BluetoothHeadsetBroadcastReceiver.onReceive: " |
+ + "a=ACTION_CONNECTION_STATE_CHANGED, " |
+ + "s=" + stateToString(state) + ", " |
+ + "n=" + device.getName() + ", " |
+ + "sb=" + isInitialStickyBroadcast() + ", " |
+ + "BT state: " + bluetoothState); |
+ if (state == BluetoothHeadset.STATE_CONNECTED) { |
+ scoConnectionAttempts = 0; |
+ updateAudioDeviceState(); |
+ } else if (state == BluetoothHeadset.STATE_CONNECTING) { |
+ // No action needed. |
+ } else if (state == BluetoothHeadset.STATE_DISCONNECTING) { |
+ // No action needed. |
+ } else if (state == BluetoothHeadset.STATE_DISCONNECTED) { |
+ // Bluetooth is probably powered off during the call. |
+ stopBluetoothSco(); |
+ updateAudioDeviceState(); |
+ } |
+ // Change in the audio (SCO) connection state of the Headset profile. |
+ } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { |
+ final int state = intent.getIntExtra( |
+ BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); |
+ final BluetoothDevice device = |
+ (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
+ Log.d(TAG_BT, "BluetoothHeadsetBroadcastReceiver.onReceive: " |
+ + "a=ACTION_AUDIO_STATE_CHANGED, " |
+ + "s=" + stateToString(state) + ", " |
+ + "n=" + device.getName() + ", " |
+ + "sb=" + isInitialStickyBroadcast() + ", " |
+ + "BT state: " + bluetoothState); |
+ if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { |
+ cancelBluetoothTimer(); |
+ if (bluetoothState == BluetoothState.SCO_CONNECTING) { |
+ bluetoothState = BluetoothState.SCO_CONNECTED; |
+ scoConnectionAttempts = 0; |
+ updateAudioDeviceState(); |
+ } else { |
+ Log.w(TAG_BT, "Unexpected state BluetoothHeadset.STATE_AUDIO_CONNECTED"); |
+ } |
+ } else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) { |
+ // No action needed. |
+ } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { |
+ if (isInitialStickyBroadcast()) { |
+ Log.d(TAG_BT, "Ignore STATE_AUDIO_DISCONNECTED initial sticky broadcast."); |
+ return; |
+ } |
+ stopBluetoothSco(); |
+ updateAudioDeviceState(); |
+ } |
+ } |
+ } |
+ }; |
+ |
+ // Implementation of an interface that notifies BluetoothProfile IPC clients when they have been |
+ // connected to or disconnected from the service. |
+ private class BluetoothServiceListener implements BluetoothProfile.ServiceListener { |
+ @Override |
+ // Called to notify the client when the proxy object has been connected to the service. |
+ public void onServiceConnected(int profile, BluetoothProfile proxy) { |
+ if (profile != BluetoothProfile.HEADSET || bluetoothState == BluetoothState.UNINITIALIZED) { |
+ return; |
+ } |
+ Log.d(TAG_BT, "BluetoothServiceListener.onServiceConnected: BT state=" + bluetoothState); |
+ // Android only supports one connected Bluetooth Headset at a time. |
+ bluetoothHeadset = (BluetoothHeadset) proxy; |
+ // Update BT state and list of available audio devices. |
+ updateAudioDeviceState(); |
} |
- // The proximity sensor should only be activated when there are exactly two |
- // available audio devices. |
- if (audioDevices.size() == 2 && audioDevices.contains(AppRTCAudioManager.AudioDevice.EARPIECE) |
- && audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) { |
- if (proximitySensor.sensorReportsNearState()) { |
- // Sensor reports that a "handset is being held up to a person's ear", |
- // or "something is covering the light sensor". |
- setAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE); |
- } else { |
- // Sensor reports that a "handset is removed from a person's ear", or |
- // "the light sensor is no longer covered". |
- setAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); |
+ @Override |
+ // Called to notify the client that this proxy object has been disconnected from the service. |
+ public void onServiceDisconnected(int profile) { |
+ if (profile != BluetoothProfile.HEADSET || bluetoothState == BluetoothState.UNINITIALIZED) { |
+ return; |
} |
+ Log.d(TAG_BT, "BluetoothServiceListener.onServiceDisconnected: BT state=" + bluetoothState); |
+ // stopBluetoothSco(); |
+ bluetoothHeadset = null; |
+ // bluetoothDevice = null; |
+ bluetoothState = BluetoothState.HEADSET_UNAVAILABLE; |
+ // Update BT state and list of available audio devices. |
+ updateAudioDeviceState(); |
} |
} |
- /** Construction */ |
- static AppRTCAudioManager create(Context context, Runnable deviceStateChangeListener) { |
- return new AppRTCAudioManager(context, deviceStateChangeListener); |
+ // Construction. |
+ static AppRTCAudioManager create(Context context) { |
+ return new AppRTCAudioManager(context); |
} |
- private AppRTCAudioManager(Context context, Runnable deviceStateChangeListener) { |
+ private AppRTCAudioManager(Context context) { |
+ Log.d(TAG, "ctor"); |
apprtcContext = context; |
- onStateChangeListener = deviceStateChangeListener; |
audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); |
+ wiredHeadsetReceiver = new WiredHeadsetReceiver(); |
+ bluetoothServiceListener = new BluetoothServiceListener(); |
+ bluetoothHeadsetReceiver = new BluetoothHeadsetBroadcastReceiver(); |
+ amState = AudioManagerState.UNINITIALIZED; |
+ |
+ final HandlerThread handlerThread = new HandlerThread(TAG); |
+ handlerThread.start(); |
magjed_webrtc
2016/11/28 12:59:45
It looks like you are only using this thread for a
henrika_webrtc
2016/11/28 14:06:18
Will see if I can resolve in ant alternative way.
magjed_webrtc
2016/11/29 19:59:52
I thought you would do more than just a timeout. J
|
+ handler = new Handler(handlerThread.getLooper()); |
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); |
useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pref_speakerphone_key), |
context.getString(R.string.pref_speakerphone_default)); |
- |
+ Log.d(TAG, "useSpeakerphone: " + useSpeakerphone); |
if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { |
defaultAudioDevice = AudioDevice.EARPIECE; |
} else { |
defaultAudioDevice = AudioDevice.SPEAKER_PHONE; |
} |
- |
- // Create and initialize the proximity sensor. |
- // Tablet devices (e.g. Nexus 7) does not support proximity sensors. |
- // Note that, the sensor will not be active until start() has been called. |
- proximitySensor = AppRTCProximitySensor.create(context, new Runnable() { |
- // This method will be called each time a state change is detected. |
- // Example: user holds his hand over the device (closer than ~5 cm), |
- // or removes his hand from the device. |
- public void run() { |
- onProximitySensorChangedState(); |
- } |
- }); |
+ Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice); |
AppRTCUtils.logDeviceInfo(TAG); |
+ |
+ // Check if this process has the BLUETOOTH permission or not. |
+ hasBluetoothPermission = hasPermission(android.Manifest.permission.BLUETOOTH); |
+ Log.d(TAG_BT, "hasBluetoothPermission: " + hasBluetoothPermission); |
} |
- public void init() { |
- Log.d(TAG, "init"); |
- if (initialized) { |
+ public void start(AudioManagerEvents audioManagerEvents) { |
+ Log.d(TAG, "start"); |
+ if (amState == AudioManagerState.RUNNING) { |
+ Log.e(TAG, "AudioManager is already active"); |
return; |
} |
+ // TODO(henrika): perhaps call new method called preInitAudio() here if UNINITIALIZED. |
+ |
+ Log.d(TAG, "AudioManager starts..."); |
+ this.audioManagerEvents = audioManagerEvents; |
+ amState = AudioManagerState.RUNNING; |
- // Store current audio state so we can restore it when close() is called. |
+ // Store current audio state so we can restore it when stop() is called. |
savedAudioMode = audioManager.getMode(); |
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); |
savedIsMicrophoneMute = audioManager.isMicrophoneMute(); |
+ hasWiredHeadset = hasWiredHeadset(); |
// Create an AudioManager.OnAudioFocusChangeListener instance. |
audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { |
@@ -199,31 +378,51 @@ public class AppRTCAudioManager { |
// Start by setting MODE_IN_COMMUNICATION as default audio mode. It is |
// required to be in this mode when playout and/or recording starts for |
// best possible VoIP performance. |
- // TODO(henrika): we migh want to start with RINGTONE mode here instead. |
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); |
// Always disable microphone mute during a WebRTC call. |
setMicrophoneMute(false); |
+ // Set initial device states. |
+ userSelectedAudioDevice = AudioDevice.NONE; |
+ selectedAudioDevice = AudioDevice.NONE; |
+ audioDevices.clear(); |
+ |
+ // Initialize and start Bluetooth if a BT device is available or initiate |
+ // detection of new (enabled) BT devices. |
+ bluetoothState = BluetoothState.UNINITIALIZED; |
+ startBluetooth(); |
+ |
// Do initial selection of audio device. This setting can later be changed |
- // either by adding/removing a wired headset or by covering/uncovering the |
- // proximity sensor. |
- updateAudioDeviceState(hasWiredHeadset()); |
+ // either by adding/removing a BT or wired headset or by covering/uncovering |
+ // the proximity sensor. |
+ updateAudioDeviceState(); |
// Register receiver for broadcast intents related to adding/removing a |
- // wired headset (Intent.ACTION_HEADSET_PLUG). |
- registerForWiredHeadsetIntentBroadcast(); |
- |
- initialized = true; |
+ // wired headset. |
+ registerReceiver(wiredHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); |
+ |
+ IntentFilter bluetoothHeadsetFilter = new IntentFilter(); |
+ // Register receiver for change in connection state of the Headset profile. |
+ bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); |
+ // Register receiver for change in audio connection state of the Headset profile. |
+ bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); |
+ registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter); |
+ Log.d(TAG, "AudioManager started"); |
} |
- public void close() { |
- Log.d(TAG, "close"); |
- if (!initialized) { |
+ public void stop() { |
+ Log.d(TAG, "stop"); |
+ if (amState != AudioManagerState.RUNNING) { |
+ Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState); |
return; |
} |
+ amState = AudioManagerState.UNINITIALIZED; |
- unregisterForWiredHeadsetIntentBroadcast(); |
+ // Cancel Bluetooth related tasks. |
+ unregisterReceiver(bluetoothHeadsetReceiver); |
+ unregisterReceiver(wiredHeadsetReceiver); |
+ stopBluetooth(); |
// Restore previously stored audio states. |
setSpeakerphoneOn(savedIsSpeakerPhoneOn); |
@@ -235,102 +434,87 @@ public class AppRTCAudioManager { |
audioFocusChangeListener = null; |
Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams"); |
- if (proximitySensor != null) { |
- proximitySensor.stop(); |
- proximitySensor = null; |
- } |
- |
- initialized = false; |
+ audioManagerEvents = null; |
+ Log.d(TAG, "AudioManager stopped"); |
} |
- /** Changes selection of the currently active audio device. */ |
- public void setAudioDevice(AudioDevice device) { |
- Log.d(TAG, "setAudioDevice(device=" + device + ")"); |
+ // Changes selection of the currently active audio device. |
+ private void setAudioDeviceInternal(AudioDevice device) { |
+ Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")"); |
AppRTCUtils.assertIsTrue(audioDevices.contains(device)); |
switch (device) { |
case SPEAKER_PHONE: |
setSpeakerphoneOn(true); |
- selectedAudioDevice = AudioDevice.SPEAKER_PHONE; |
break; |
case EARPIECE: |
setSpeakerphoneOn(false); |
- selectedAudioDevice = AudioDevice.EARPIECE; |
break; |
case WIRED_HEADSET: |
setSpeakerphoneOn(false); |
- selectedAudioDevice = AudioDevice.WIRED_HEADSET; |
+ break; |
+ case BLUETOOTH: |
+ setSpeakerphoneOn(false); |
break; |
default: |
Log.e(TAG, "Invalid audio device selection"); |
break; |
} |
- onAudioManagerChangedState(); |
+ selectedAudioDevice = device; |
} |
- /** Returns current set of available/selectable audio devices. */ |
- public Set<AudioDevice> getAudioDevices() { |
- return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); |
+ // Changes default audio device. |
+ // TODO(henrika): add usage of this method in the AppRTCMobile client. |
+ public synchronized void setDefaultAudioDevice(AudioDevice defaultDevice) { |
+ switch (defaultDevice) { |
+ case SPEAKER_PHONE: |
+ defaultAudioDevice = defaultDevice; |
+ break; |
+ case EARPIECE: |
+ if (hasEarpiece()) { |
+ defaultAudioDevice = defaultDevice; |
+ } else { |
+ defaultAudioDevice = AudioDevice.SPEAKER_PHONE; |
+ } |
+ break; |
+ default: |
+ Log.e(TAG, "Invalid default audio device selection"); |
+ break; |
+ } |
+ Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")"); |
+ updateAudioDeviceState(); |
} |
- /** Returns the currently selected audio device. */ |
- public AudioDevice getSelectedAudioDevice() { |
- return selectedAudioDevice; |
+ // Changes selection of the currently active audio device. |
+ public synchronized void selectAudioDevice(AudioDevice device) { |
+ if (!audioDevices.contains(device)) { |
+ Log.e(TAG, "Can not select " + device + " from available " + audioDevices); |
+ } |
+ userSelectedAudioDevice = device; |
+ updateAudioDeviceState(); |
} |
- /** |
- * Registers receiver for the broadcasted intent when a wired headset is |
- * plugged in or unplugged. The received intent will have an extra |
- * 'state' value where 0 means unplugged, and 1 means plugged. |
- */ |
- private void registerForWiredHeadsetIntentBroadcast() { |
- IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); |
- |
- /** Receiver which handles changes in wired headset availability. */ |
- wiredHeadsetReceiver = new BroadcastReceiver() { |
- private static final int STATE_UNPLUGGED = 0; |
- private static final int STATE_PLUGGED = 1; |
- private static final int HAS_NO_MIC = 0; |
- private static final int HAS_MIC = 1; |
+ // Returns current set of available/selectable audio devices. |
+ public synchronized Set<AudioDevice> getAudioDevices() { |
+ return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); |
+ } |
- @Override |
- public void onReceive(Context context, Intent intent) { |
- int state = intent.getIntExtra("state", STATE_UNPLUGGED); |
- int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); |
- String name = intent.getStringExtra("name"); |
- Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": " |
- + "a=" + intent.getAction() + ", s=" |
- + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" |
- + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb=" |
- + isInitialStickyBroadcast()); |
- |
- boolean hasWiredHeadset = (state == STATE_PLUGGED); |
- switch (state) { |
- case STATE_UNPLUGGED: |
- updateAudioDeviceState(hasWiredHeadset); |
- break; |
- case STATE_PLUGGED: |
- if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) { |
- updateAudioDeviceState(hasWiredHeadset); |
- } |
- break; |
- default: |
- Log.e(TAG, "Invalid state"); |
- break; |
- } |
- } |
- }; |
+ // Returns the currently selected audio device. |
+ public synchronized AudioDevice getSelectedAudioDevice() { |
+ return selectedAudioDevice; |
+ } |
- apprtcContext.registerReceiver(wiredHeadsetReceiver, filter); |
+ // Helper method for receiver registration. |
+ private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { |
+ apprtcContext.registerReceiver(receiver, filter); |
} |
- /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ |
- private void unregisterForWiredHeadsetIntentBroadcast() { |
- apprtcContext.unregisterReceiver(wiredHeadsetReceiver); |
- wiredHeadsetReceiver = null; |
+ // 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) { |
@@ -339,7 +523,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) { |
@@ -348,72 +532,393 @@ public class AppRTCAudioManager { |
audioManager.setMicrophoneMute(on); |
} |
- /** Gets the current earpiece state. */ |
+ // Starts all Bluetooth related tasks. |
+ void startBluetooth() { |
+ Log.d(TAG_BT, "startBluetooth"); |
+ if (!hasBluetoothPermission) { |
+ Log.w(TAG_BT, "Process lacks BLUETOOTH permission"); |
+ return; |
+ } |
+ if (bluetoothState != BluetoothState.UNINITIALIZED) { |
+ Log.w(TAG_BT, "Invalid BT state"); |
+ return; |
+ } |
+ bluetoothHeadset = null; |
+ bluetoothDevice = null; |
+ scoConnectionAttempts = 0; |
+ // Get a handle to the default local Bluetooth adapter. |
+ bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); |
+ if (bluetoothAdapter == null) { |
+ Log.w(TAG_BT, "Device does not support Bluetooth"); |
+ return; |
+ } |
+ // Ensure that the device supports use of BT SCO audio for off call use cases. |
+ if (!audioManager.isBluetoothScoAvailableOffCall()) { |
+ Log.e(TAG_BT, "Bluetooth SCO audio is not available off call"); |
+ return; |
+ } |
+ logBluetoothAdapterInfo(bluetoothAdapter); |
+ // Establish a connection to the HEADSET profile (includes both Bluetooth Headset and |
+ // Hands-Free) proxy object and install a listener. |
+ if (!bluetoothAdapter.getProfileProxy( |
+ apprtcContext, bluetoothServiceListener, BluetoothProfile.HEADSET)) { |
+ Log.e(TAG_BT, "BluetoothAdapter.getProfileProxy(HEADSET) failed"); |
+ return; |
+ } |
+ Log.d(TAG_BT, "HEADSET profile state: " |
+ + stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET))); |
+ Log.d(TAG_BT, "Bluetooth proxy for headset profile has started"); |
+ bluetoothState = BluetoothState.HEADSET_UNAVAILABLE; |
+ } |
+ |
+ // Stops all Bluetooth tasks. |
+ void stopBluetooth() { |
+ Log.d(TAG_BT, "stopBluetooth: BT state=" + bluetoothState); |
+ if (bluetoothAdapter != null) { |
+ // Stop BT SCO connection with remote device if needed. |
+ stopBluetoothSco(); |
+ // Close down remaining BT resources. |
+ if (bluetoothState != BluetoothState.UNINITIALIZED) { |
+ cancelBluetoothTimer(); |
+ if (bluetoothHeadset != null) { |
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset); |
+ bluetoothHeadset = null; |
+ } |
+ bluetoothAdapter = null; |
+ bluetoothDevice = null; |
+ bluetoothState = BluetoothState.UNINITIALIZED; |
+ } |
+ } |
+ } |
+ |
+ // Called when start of the BT SCO channel takes too long time. Usually |
+ // happens when the BT device has been turned on during an ongoing call. |
+ private void bluetoothTimeout() { |
+ if (amState != AudioManagerState.RUNNING || bluetoothState == BluetoothState.UNINITIALIZED |
+ || bluetoothHeadset == null) { |
+ return; |
+ } |
+ Log.d(TAG_BT, "bluetoothTimeout: BT state=" + bluetoothState); |
+ if (bluetoothState != BluetoothState.SCO_CONNECTING) { |
+ return; |
+ } |
+ // Bluetooth SCO should be connecting; check the latest result. |
+ boolean scoConnected = false; |
+ List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices(); |
+ if (devices.size() > 0) { |
+ bluetoothDevice = devices.get(0); |
+ if (bluetoothHeadset.isAudioConnected(bluetoothDevice)) { |
+ Log.d(TAG_BT, "SCO connected with " + bluetoothDevice.getName()); |
+ scoConnected = true; |
+ } else { |
+ Log.d(TAG_BT, "SCO is not connected with " + bluetoothDevice.getName()); |
+ } |
+ } |
+ |
+ if (scoConnected) { |
+ // We thought BT had timed out, but it's actually on; updating state. |
+ bluetoothState = BluetoothState.SCO_CONNECTED; |
+ scoConnectionAttempts = 0; |
+ updateAudioDeviceState(); |
+ } else { |
+ // Give up and "cancel" our request by calling stopBluetoothSco(). |
+ Log.w(TAG_BT, "BT failed to connect after timeout"); |
+ stopBluetoothSco(); |
+ updateAudioDeviceState(); |
+ } |
+ } |
+ |
+ private void startBluetoothTimer() { |
+ Log.d(TAG_BT, "startBluetoothTimer"); |
+ handler.postDelayed(bluetoothTimeoutRunnable, BLUETOOTH_SCO_TIMEOUT_MS); |
+ } |
+ |
+ private void cancelBluetoothTimer() { |
+ Log.d(TAG_BT, "cancelBluetoothTimer"); |
+ handler.removeCallbacks(bluetoothTimeoutRunnable); |
+ } |
+ |
+ // Starts Bluetooth SCO connection with remote device. |
+ // Note that the phone application always has the priority on the usage of the SCO connection |
+ // for telephony. If this method is called while the phone is in call it will be ignored. |
+ // Similarly, if a call is received or sent while an application is using the SCO connection, |
+ // the connection will be lost for the application and NOT returned automatically when the call |
+ // ends. Also note that: up to and including API version JELLY_BEAN_MR1, this method initiates a |
+ // virtual voice call to the Bluetooth headset. After API version JELLY_BEAN_MR2 only a raw SCO |
+ // audio connection is established. |
+ // TODO(henrika): should we add support for virtual voice call to BT headset also for JBMR2 and |
+ // higher. It might be required to initiates a virtual voice call since many devices do not |
+ // accept SCO audio without a "call". |
+ private void startBluetoothSco() { |
+ if (bluetoothState != BluetoothState.HEADSET_AVAILABLE) { |
+ return; |
+ } |
+ Log.d(TAG_BT, "startBluetoothSco: BT state=" + bluetoothState + ", " |
+ + "attempt: " + scoConnectionAttempts + ", " |
+ + "SCO is on: " + audioManager.isBluetoothScoOn()); |
+ // The SCO connection establishment can take several seconds, hence we cannot rely on the |
+ // connection to be available when the method returns but instead register to receive the |
+ // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AUDIO_STATE_CONNECTED. |
+ bluetoothState = BluetoothState.SCO_CONNECTING; |
+ audioManager.startBluetoothSco(); |
+ scoConnectionAttempts++; |
+ startBluetoothTimer(); |
+ } |
+ |
+ // Stops Bluetooth SCO connection with remote device. |
+ private void stopBluetoothSco() { |
+ if (bluetoothState != BluetoothState.SCO_CONNECTING |
+ && bluetoothState != BluetoothState.SCO_CONNECTED) { |
+ return; |
+ } |
+ Log.d(TAG_BT, "stopBluetoothSco: BT state=" + bluetoothState + ", " |
+ + "SCO is on: " + audioManager.isBluetoothScoOn()); |
+ cancelBluetoothTimer(); |
+ audioManager.stopBluetoothSco(); |
+ bluetoothState = BluetoothState.SCO_DISCONNECTING; |
+ } |
+ |
+ // Use the BluetoothHeadset proxy object (controls the Bluetooth Headset Service via IPC) to |
+ // update the list of connected devices for the HEADSET profile. |
+ private void updateBluetoothDevices() { |
+ if (amState != AudioManagerState.RUNNING || bluetoothState == BluetoothState.UNINITIALIZED |
+ || bluetoothHeadset == null) { |
+ return; |
+ } |
+ Log.d(TAG_BT, "updateBluetoothDevices"); |
+ // Update the Bluetooth device list for the headset profile. |
+ // The BluetoothDevice class is just a thin wrapper for a Bluetooth hardware address. |
+ List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices(); |
+ if (devices.size() == 0) { |
+ bluetoothDevice = null; |
+ } else { |
+ bluetoothDevice = devices.get(0); |
+ } |
+ if (bluetoothDevice == null) { |
+ bluetoothState = BluetoothState.HEADSET_UNAVAILABLE; |
+ Log.d(TAG_BT, "No connected bluetooth headset"); |
+ } else { |
+ bluetoothState = BluetoothState.HEADSET_AVAILABLE; |
+ Log.d(TAG_BT, "Connected bluetooth headset: " |
+ + "name=" + bluetoothDevice.getName() + ", " |
+ + "state=" + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice)) |
+ + ", SCO audio=" + bluetoothHeadset.isAudioConnected(bluetoothDevice)); |
+ } |
+ Log.d(TAG_BT, "Updated Bluetooth state: " + bluetoothState); |
+ } |
+ |
+ // 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() { |
return audioManager.isWiredHeadsetOn(); |
} |
- /** Update list of possible audio devices and make new device selection. */ |
- private void updateAudioDeviceState(boolean hasWiredHeadset) { |
- // Update the list of available audio devices. |
- audioDevices.clear(); |
+ // Update list of possible audio devices and make new device selection. |
+ private void updateAudioDeviceState() { |
+ Log.d(TAG, "--- updateAudioDeviceState: " |
+ + "wired headset=" + hasWiredHeadset + ", " |
+ + "BT state=" + bluetoothState); |
+ Log.d(TAG, "Device status: " |
+ + "available=" + audioDevices + ", " |
+ + "selected=" + selectedAudioDevice + ", " |
+ + "user selected=" + userSelectedAudioDevice); |
+ |
+ // Update Bluetooth device list. |
+ if (bluetoothState == BluetoothState.HEADSET_AVAILABLE |
+ || bluetoothState == BluetoothState.HEADSET_UNAVAILABLE |
+ || bluetoothState == BluetoothState.SCO_DISCONNECTING) { |
+ updateBluetoothDevices(); |
+ } |
+ |
+ // Update the set of available audio devices. |
+ Set<AudioDevice> newAudioDevices = new HashSet<>(); |
+ |
+ if (bluetoothState == BluetoothState.SCO_CONNECTED |
+ || bluetoothState == BluetoothState.SCO_CONNECTING |
+ || bluetoothState == BluetoothState.HEADSET_AVAILABLE) { |
+ newAudioDevices.add(AudioDevice.BLUETOOTH); |
+ } |
if (hasWiredHeadset) { |
// If a wired headset is connected, then it is the only possible option. |
- audioDevices.add(AudioDevice.WIRED_HEADSET); |
+ 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). |
- audioDevices.add(AudioDevice.SPEAKER_PHONE); |
+ newAudioDevices.add(AudioDevice.SPEAKER_PHONE); |
if (hasEarpiece()) { |
- audioDevices.add(AudioDevice.EARPIECE); |
+ newAudioDevices.add(AudioDevice.EARPIECE); |
} |
} |
- Log.d(TAG, "audioDevices: " + audioDevices); |
- // Switch to correct audio device given the list of available audio devices. |
- if (hasWiredHeadset) { |
- setAudioDevice(AudioDevice.WIRED_HEADSET); |
+ // Update the existing audio device set if needed. |
+ boolean audioDeviceSetUpdated = |
+ !audioDevices.containsAll(newAudioDevices) || !newAudioDevices.containsAll(audioDevices); |
+ if (audioDeviceSetUpdated) { |
+ audioDevices.clear(); |
+ audioDevices.addAll(newAudioDevices); |
+ } |
+ |
+ // Correct user selected audio devices if needed. |
+ if (bluetoothState == BluetoothState.HEADSET_UNAVAILABLE |
+ && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { |
+ // If BT is not available, it can't be the user selection. |
+ 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. |
+ 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. |
+ 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 needBluetoothStart = bluetoothState == BluetoothState.HEADSET_AVAILABLE |
+ && (userSelectedAudioDevice == AudioDevice.NONE |
+ || userSelectedAudioDevice == AudioDevice.BLUETOOTH); |
+ |
+ // Need to stop Bluetooth if user selected different device and Bluetooth SCO connection is |
+ // established or in the process. |
+ boolean needBluetoothStop = (bluetoothState == BluetoothState.SCO_CONNECTED |
+ || bluetoothState == BluetoothState.SCO_CONNECTING) |
+ && (userSelectedAudioDevice != AudioDevice.NONE |
+ && userSelectedAudioDevice != AudioDevice.BLUETOOTH); |
+ |
+ if (bluetoothState == BluetoothState.HEADSET_AVAILABLE |
+ || bluetoothState == BluetoothState.SCO_CONNECTING |
+ || bluetoothState == BluetoothState.SCO_CONNECTED) { |
+ Log.d(TAG_BT, "Need BT: start=" + needBluetoothStart + ", " |
+ + "stop=" + needBluetoothStop + ", " |
+ + "BT state=" + bluetoothState); |
+ } |
+ |
+ // Start/stop Bluetooth SCO connection |
+ if (needBluetoothStop) { |
+ stopBluetoothSco(); |
+ updateBluetoothDevices(); |
+ } |
+ |
+ if (needBluetoothStart && !needBluetoothStop) { |
+ if (scoConnectionAttempts < MAX_SCO_CONNECTION_ATTEMPTS) { |
+ // Starts BT SCO channel and waits for ACTION_AUDIO_STATE_CHANGED. |
+ Log.d(TAG_BT, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_CHANGED..."); |
+ startBluetoothSco(); |
+ } else { |
+ Log.e(TAG_BT, "BT SCO connection fails - no more attempts"); |
+ audioDevices.remove(AudioDevice.BLUETOOTH); |
+ audioDeviceSetUpdated = true; |
+ } |
+ } |
+ |
+ // Update selected audio device. |
+ AudioDevice newAudioDevice = selectedAudioDevice; |
+ |
+ if (bluetoothState == BluetoothState.SCO_CONNECTED) { |
+ // If a Bluetooth is connected, then it should be used as output audio device. |
+ newAudioDevice = AudioDevice.BLUETOOTH; |
+ } else if (hasWiredHeadset) { |
+ // If a wired headset is connected, but Bluetooth is not, then wired headset is used as |
+ // output device. |
+ newAudioDevice = AudioDevice.WIRED_HEADSET; |
} else { |
- setAudioDevice(defaultAudioDevice); |
+ // 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; |
+ } |
+ // Switch to new device but only if there has been any changes. |
+ if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) { |
+ // Do the required device switch. |
+ setAudioDeviceInternal(newAudioDevice); |
+ Log.d(TAG, "New device status: " |
+ + "available=" + audioDevices + ", " |
+ + "selected=" + newAudioDevice); |
+ if (audioManagerEvents != null) { |
+ // Notify a listening client that audio device has been changed. |
+ audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices); |
+ } |
} |
+ Log.d(TAG, "--- updateAudioDeviceState done"); |
} |
- /** Called each time a new audio device has been added or removed. */ |
- private void onAudioManagerChangedState() { |
- Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices + ", selected=" |
- + selectedAudioDevice); |
- |
- // Enable the proximity sensor if there are two available audio devices |
- // in the list. Given the current implementation, we know that the choice |
- // will then be between EARPIECE and SPEAKER_PHONE. |
- if (audioDevices.size() == 2) { |
- AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE) |
- && audioDevices.contains(AudioDevice.SPEAKER_PHONE)); |
- // Start the proximity sensor. |
- proximitySensor.start(); |
- } else if (audioDevices.size() == 1) { |
- // Stop the proximity sensor since it is no longer needed. |
- proximitySensor.stop(); |
- } else { |
- Log.e(TAG, "Invalid device list"); |
+ // Checks if the process has as specified permission or not. |
+ private boolean hasPermission(String permission) { |
+ return apprtcContext.checkPermission(permission, Process.myPid(), Process.myUid()) |
+ == PackageManager.PERMISSION_GRANTED; |
+ } |
+ |
+ private String stateToString(int state) { |
+ String stateString; |
+ switch (state) { |
+ case BluetoothAdapter.STATE_DISCONNECTED: |
+ // The profile is in disconnected state. |
+ stateString = "DISCONNECTED"; |
+ break; |
+ case BluetoothAdapter.STATE_CONNECTED: |
+ // The profile is in connected state. |
+ stateString = "CONNECTED"; |
+ break; |
+ case BluetoothAdapter.STATE_CONNECTING: |
+ // The profile is in connecting state. |
+ stateString = "CONNECTING"; |
+ break; |
+ case BluetoothAdapter.STATE_DISCONNECTING: |
+ // The profile is in disconnecting state. |
+ stateString = "DISCONNECTING"; |
+ break; |
+ case BluetoothAdapter.STATE_OFF: |
+ // Indicates the local Bluetooth adapter is off. |
+ stateString = "OFF"; |
+ break; |
+ case BluetoothAdapter.STATE_ON: |
+ // Indicates the local Bluetooth adapter is on, and ready for use. |
+ stateString = "ON"; |
+ break; |
+ case BluetoothAdapter.STATE_TURNING_OFF: |
+ // Indicates the local Bluetooth adapter is turning off. Local clients should immediately |
+ // attempt graceful disconnection of any remote links. |
+ stateString = "TURNING_OFF"; |
+ break; |
+ case BluetoothAdapter.STATE_TURNING_ON: |
+ // Indicates the local Bluetooth adapter is turning on. However local clients should wait |
+ // for STATE_ON before attempting to use the adapter. |
+ stateString = "TURNING_ON"; |
+ break; |
+ default: |
+ stateString = "INVALID"; |
+ break; |
} |
+ return stateString; |
+ } |
- if (onStateChangeListener != null) { |
- // Run callback to notify a listening client. The client can then |
- // use public getters to query the new state. |
- onStateChangeListener.run(); |
+ // Logs the state of the local Bluetooth adapter. |
+ private void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) { |
+ Log.d(TAG_BT, "BluetoothAdapter: " |
+ + "enabled=" + localAdapter.isEnabled() + ", " |
+ + "state=" + stateToString(localAdapter.getState()) + ", " |
+ + "name=" + localAdapter.getName() + ", " |
+ + "address=" + localAdapter.getAddress()); |
+ // Log the set of BluetoothDevice objects that are bonded (paired) to the local adapter. |
+ Set<BluetoothDevice> pairedDevices = localAdapter.getBondedDevices(); |
+ if (pairedDevices.size() > 0) { |
+ Log.d(TAG_BT, "paired devices:"); |
+ for (BluetoothDevice device : pairedDevices) { |
+ Log.d(TAG_BT, " name=" + device.getName() + ", address=" + device.getAddress()); |
+ } |
} |
} |
} |