|
@@ -13,46 +13,26 @@ import android.os.Handler;
|
13
|
13
|
import android.os.Looper;
|
14
|
14
|
import android.util.Log;
|
15
|
15
|
|
16
|
|
-import com.facebook.react.bridge.ReactContext;
|
17
|
|
-
|
18
|
16
|
/**
|
19
|
17
|
* Helper class to detect and handle Bluetooth device changes. It monitors
|
20
|
18
|
* Bluetooth headsets being connected / disconnected and notifies the module
|
21
|
19
|
* about device changes when this occurs.
|
22
|
20
|
*/
|
23
|
21
|
public class BluetoothHeadsetMonitor {
|
24
|
|
- /**
|
25
|
|
- * {@link AudioManager} instance used to interact with the Android audio
|
26
|
|
- * subsystem.
|
27
|
|
- */
|
28
|
|
- private final AudioManager audioManager;
|
29
|
|
-
|
30
|
22
|
/**
|
31
|
23
|
* {@link AudioModeModule} where this monitor reports.
|
32
|
24
|
*/
|
33
|
25
|
private final AudioModeModule audioModeModule;
|
34
|
26
|
|
35
|
27
|
/**
|
36
|
|
- * Reference to the Bluetooth adapter, needed for managing
|
37
|
|
- * <tt>BluetoothProfile.HEADSET</tt> devices.
|
|
28
|
+ * The {@link Context} in which {@link #audioModeModule} executes.
|
38
|
29
|
*/
|
39
|
|
- private BluetoothAdapter bluetoothAdapter;
|
|
30
|
+ private final Context context;
|
40
|
31
|
|
41
|
32
|
/**
|
42
|
33
|
* Reference to a proxy object which allows us to query connected devices.
|
43
|
34
|
*/
|
44
|
|
- private BluetoothHeadset bluetoothHeadset;
|
45
|
|
-
|
46
|
|
- /**
|
47
|
|
- * Listener for Bluetooth service profiles, allows us to get the proxy
|
48
|
|
- * object to {@link BluetoothHeadset}.
|
49
|
|
- */
|
50
|
|
- private BluetoothProfile.ServiceListener bluetoothProfileListener;
|
51
|
|
-
|
52
|
|
- /**
|
53
|
|
- * Helper for running Bluetooth operations on the main thread.
|
54
|
|
- */
|
55
|
|
- private Runnable bluetoothRunnable;
|
|
35
|
+ private BluetoothHeadset headset;
|
56
|
36
|
|
57
|
37
|
/**
|
58
|
38
|
* Flag indicating if there are any Bluetooth headset devices currently
|
|
@@ -63,143 +43,140 @@ public class BluetoothHeadsetMonitor {
|
63
|
43
|
/**
|
64
|
44
|
* {@link Handler} for running all operations on the main thread.
|
65
|
45
|
*/
|
66
|
|
- private final Handler mainThreadHandler;
|
|
46
|
+ private final Handler mainThreadHandler
|
|
47
|
+ = new Handler(Looper.getMainLooper());
|
67
|
48
|
|
68
|
49
|
/**
|
69
|
|
- * {@link ReactContext} instance where the main module runs.
|
70
|
|
- */
|
71
|
|
- private final ReactContext reactContext;
|
72
|
|
-
|
73
|
|
- public BluetoothHeadsetMonitor(
|
74
|
|
- AudioModeModule audioModeModule,
|
75
|
|
- ReactContext reactContext) {
|
76
|
|
- this.audioModeModule = audioModeModule;
|
77
|
|
- this.reactContext = reactContext;
|
78
|
|
-
|
79
|
|
- audioManager
|
80
|
|
- = (AudioManager)
|
81
|
|
- reactContext.getSystemService(Context.AUDIO_SERVICE);
|
82
|
|
- mainThreadHandler = new Handler(Looper.getMainLooper());
|
83
|
|
- }
|
84
|
|
-
|
85
|
|
- /**
|
86
|
|
- * Returns the current headset availability.
|
87
|
|
- *
|
88
|
|
- * @return true if there is a Bluetooth headset connected, false otherwise.
|
89
|
|
- */
|
90
|
|
- public boolean isHeadsetAvailable() {
|
91
|
|
- return headsetAvailable;
|
92
|
|
- }
|
93
|
|
-
|
94
|
|
- /**
|
95
|
|
- * Start monitoring Bluetooth device activity.
|
|
50
|
+ * Helper for running Bluetooth operations on the main thread.
|
96
|
51
|
*/
|
97
|
|
- public void start() {
|
98
|
|
- bluetoothRunnable = new Runnable() {
|
|
52
|
+ private final Runnable updateDevicesRunnable
|
|
53
|
+ = new Runnable() {
|
99
|
54
|
@Override
|
100
|
55
|
public void run() {
|
101
|
|
- if (bluetoothHeadset == null) {
|
102
|
|
- headsetAvailable = false;
|
103
|
|
- } else {
|
104
|
|
- headsetAvailable
|
105
|
|
- = !bluetoothHeadset.getConnectedDevices().isEmpty();
|
106
|
|
- }
|
|
56
|
+ headsetAvailable
|
|
57
|
+ = (headset != null)
|
|
58
|
+ && !headset.getConnectedDevices().isEmpty();
|
107
|
59
|
audioModeModule.onAudioDeviceChange();
|
108
|
60
|
}
|
109
|
61
|
};
|
110
|
62
|
|
111
|
|
- bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
112
|
|
- if (bluetoothAdapter == null) {
|
113
|
|
- Log.w(AudioModeModule.TAG, "Device doesn't support Bluetooth");
|
114
|
|
- return;
|
115
|
|
- }
|
|
63
|
+ public BluetoothHeadsetMonitor(
|
|
64
|
+ AudioModeModule audioModeModule,
|
|
65
|
+ Context context) {
|
|
66
|
+ this.audioModeModule = audioModeModule;
|
|
67
|
+ this.context = context;
|
|
68
|
+
|
|
69
|
+ AudioManager audioManager
|
|
70
|
+ = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
116
|
71
|
|
117
|
72
|
if (!audioManager.isBluetoothScoAvailableOffCall()) {
|
118
|
73
|
Log.w(AudioModeModule.TAG, "Bluetooth SCO is not available");
|
119
|
74
|
return;
|
120
|
75
|
}
|
121
|
76
|
|
|
77
|
+ if (getBluetoothHeadsetProfileProxy()) {
|
|
78
|
+ registerBluetoothReceiver();
|
|
79
|
+
|
|
80
|
+ // Initial detection.
|
|
81
|
+ updateDevices();
|
|
82
|
+ }
|
|
83
|
+ }
|
|
84
|
+
|
|
85
|
+ private boolean getBluetoothHeadsetProfileProxy() {
|
|
86
|
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
|
87
|
+
|
|
88
|
+ if (adapter == null) {
|
|
89
|
+ Log.w(AudioModeModule.TAG, "Device doesn't support Bluetooth");
|
|
90
|
+ return false;
|
|
91
|
+ }
|
|
92
|
+
|
122
|
93
|
// XXX: The profile listener listens for system services of the given
|
123
|
94
|
// type being available to the application. That is, if our Bluetooth
|
124
|
95
|
// adapter has the "headset" profile.
|
125
|
|
- bluetoothProfileListener = new BluetoothProfile.ServiceListener() {
|
126
|
|
- @Override
|
127
|
|
- public void onServiceConnected(
|
128
|
|
- int profile,
|
129
|
|
- BluetoothProfile proxy) {
|
130
|
|
- if (profile == BluetoothProfile.HEADSET) {
|
131
|
|
- bluetoothHeadset = (BluetoothHeadset) proxy;
|
132
|
|
- updateDevices();
|
|
96
|
+ BluetoothProfile.ServiceListener listener
|
|
97
|
+ = new BluetoothProfile.ServiceListener() {
|
|
98
|
+ @Override
|
|
99
|
+ public void onServiceConnected(
|
|
100
|
+ int profile,
|
|
101
|
+ BluetoothProfile proxy) {
|
|
102
|
+ if (profile == BluetoothProfile.HEADSET) {
|
|
103
|
+ headset = (BluetoothHeadset) proxy;
|
|
104
|
+ updateDevices();
|
|
105
|
+ }
|
133
|
106
|
}
|
134
|
|
- }
|
135
|
107
|
|
136
|
|
- @Override
|
137
|
|
- public void onServiceDisconnected(int profile) {
|
138
|
|
- if (profile == BluetoothProfile.HEADSET) {
|
139
|
|
- bluetoothHeadset = null;
|
140
|
|
- updateDevices();
|
|
108
|
+ @Override
|
|
109
|
+ public void onServiceDisconnected(int profile) {
|
|
110
|
+ // The logic is the same as the logic of onServiceConnected.
|
|
111
|
+ onServiceConnected(profile, /* proxy */ null);
|
141
|
112
|
}
|
142
|
|
- }
|
143
|
|
- };
|
|
113
|
+ };
|
|
114
|
+
|
|
115
|
+ return
|
|
116
|
+ adapter.getProfileProxy(
|
|
117
|
+ context,
|
|
118
|
+ listener,
|
|
119
|
+ BluetoothProfile.HEADSET);
|
|
120
|
+ }
|
144
|
121
|
|
145
|
|
- bluetoothAdapter.getProfileProxy(reactContext,
|
146
|
|
- bluetoothProfileListener, BluetoothProfile.HEADSET);
|
|
122
|
+ /**
|
|
123
|
+ * Returns the current headset availability.
|
|
124
|
+ *
|
|
125
|
+ * @return true if there is a Bluetooth headset connected, false otherwise.
|
|
126
|
+ */
|
|
127
|
+ public boolean isHeadsetAvailable() {
|
|
128
|
+ return headsetAvailable;
|
|
129
|
+ }
|
|
130
|
+
|
|
131
|
+ private void onBluetoothReceiverReceive(Context context, Intent intent) {
|
|
132
|
+ final String action = intent.getAction();
|
|
133
|
+
|
|
134
|
+ if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
|
|
135
|
+ // XXX: This action will be fired when a Bluetooth headset is
|
|
136
|
+ // connected or disconnected to the system. This is not related to
|
|
137
|
+ // audio routing.
|
|
138
|
+ int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -99);
|
|
139
|
+
|
|
140
|
+ switch (state) {
|
|
141
|
+ case BluetoothHeadset.STATE_CONNECTED:
|
|
142
|
+ case BluetoothHeadset.STATE_DISCONNECTED:
|
|
143
|
+ Log.d(
|
|
144
|
+ AudioModeModule.TAG,
|
|
145
|
+ "BT headset connection state changed: " + state);
|
|
146
|
+ updateDevices();
|
|
147
|
+ break;
|
|
148
|
+ }
|
|
149
|
+ } else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
|
|
150
|
+ // XXX: This action will be fired when the connection established
|
|
151
|
+ // with a Bluetooth headset (called a SCO connection) changes state.
|
|
152
|
+ // When the SCO connection is active we route audio to it.
|
|
153
|
+ int state
|
|
154
|
+ = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
|
|
155
|
+
|
|
156
|
+ switch (state) {
|
|
157
|
+ case AudioManager.SCO_AUDIO_STATE_CONNECTED:
|
|
158
|
+ case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
|
|
159
|
+ Log.d(
|
|
160
|
+ AudioModeModule.TAG,
|
|
161
|
+ "BT SCO connection state changed: " + state);
|
|
162
|
+ updateDevices();
|
|
163
|
+ break;
|
|
164
|
+ }
|
|
165
|
+ }
|
|
166
|
+ }
|
147
|
167
|
|
148
|
|
- IntentFilter bluetoothFilter = new IntentFilter();
|
149
|
|
- bluetoothFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
|
150
|
|
- bluetoothFilter.addAction(
|
151
|
|
- BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
152
|
|
- BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
|
|
168
|
+ private void registerBluetoothReceiver() {
|
|
169
|
+ BroadcastReceiver receiver = new BroadcastReceiver() {
|
153
|
170
|
@Override
|
154
|
171
|
public void onReceive(Context context, Intent intent) {
|
155
|
|
- final String action = intent.getAction();
|
156
|
|
- if (action.equals(
|
157
|
|
- BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
|
158
|
|
- // XXX: This action will be fired when a Bluetooth headset
|
159
|
|
- // is connected or disconnected to the system. This is not
|
160
|
|
- // related to audio routing.
|
161
|
|
- final int state
|
162
|
|
- = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -99);
|
163
|
|
- switch (state) {
|
164
|
|
- case BluetoothHeadset.STATE_CONNECTED:
|
165
|
|
- case BluetoothHeadset.STATE_DISCONNECTED:
|
166
|
|
- Log.d(
|
167
|
|
- AudioModeModule.TAG,
|
168
|
|
- "BT headset connection state changed: "
|
169
|
|
- + state);
|
170
|
|
- updateDevices();
|
171
|
|
- break;
|
172
|
|
- default:
|
173
|
|
- break;
|
174
|
|
- }
|
175
|
|
- } else if (action.equals(
|
176
|
|
- AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
|
177
|
|
- // XXX: This action will be fired when the connection
|
178
|
|
- // established with a Bluetooth headset (called a SCO
|
179
|
|
- // connection) changes state. When the SCO connection is
|
180
|
|
- // active we route audio to it.
|
181
|
|
- final int state
|
182
|
|
- = intent.getIntExtra(
|
183
|
|
- AudioManager.EXTRA_SCO_AUDIO_STATE,
|
184
|
|
- -99);
|
185
|
|
- switch (state) {
|
186
|
|
- case AudioManager.SCO_AUDIO_STATE_CONNECTED:
|
187
|
|
- case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
|
188
|
|
- Log.d(
|
189
|
|
- AudioModeModule.TAG,
|
190
|
|
- "BT SCO connection state changed: " + state);
|
191
|
|
- updateDevices();
|
192
|
|
- break;
|
193
|
|
- default:
|
194
|
|
- break;
|
195
|
|
- }
|
196
|
|
- }
|
|
172
|
+ onBluetoothReceiverReceive(context, intent);
|
197
|
173
|
}
|
198
|
174
|
};
|
199
|
|
- reactContext.registerReceiver(bluetoothReceiver, bluetoothFilter);
|
|
175
|
+ IntentFilter filter = new IntentFilter();
|
200
|
176
|
|
201
|
|
- // Initial detection.
|
202
|
|
- updateDevices();
|
|
177
|
+ filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
|
|
178
|
+ filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
|
179
|
+ context.registerReceiver(receiver, filter);
|
203
|
180
|
}
|
204
|
181
|
|
205
|
182
|
/**
|
|
@@ -207,6 +184,6 @@ public class BluetoothHeadsetMonitor {
|
207
|
184
|
* {@link AudioModeModule#onAudioDeviceChange()} callback.
|
208
|
185
|
*/
|
209
|
186
|
private void updateDevices() {
|
210
|
|
- mainThreadHandler.post(bluetoothRunnable);
|
|
187
|
+ mainThreadHandler.post(updateDevicesRunnable);
|
211
|
188
|
}
|
212
|
189
|
}
|