|
|
@@ -1,5 +1,5 @@
|
|
1
|
1
|
/*
|
|
2
|
|
- * Copyright @ 2017-present Atlassian Pty Ltd
|
|
|
2
|
+ * Copyright @ 2017-present 8x8, Inc.
|
|
3
|
3
|
*
|
|
4
|
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
5
|
* you may not use this file except in compliance with the License.
|
|
|
@@ -16,16 +16,8 @@
|
|
16
|
16
|
|
|
17
|
17
|
package org.jitsi.meet.sdk;
|
|
18
|
18
|
|
|
19
|
|
-import android.annotation.TargetApi;
|
|
20
|
|
-import android.content.BroadcastReceiver;
|
|
21
|
19
|
import android.content.Context;
|
|
22
|
|
-import android.content.Intent;
|
|
23
|
|
-import android.content.IntentFilter;
|
|
24
|
|
-import android.content.pm.PackageManager;
|
|
25
|
|
-import android.media.AudioDeviceInfo;
|
|
26
|
|
-import android.media.AudioManager;
|
|
27
|
20
|
import android.os.Build;
|
|
28
|
|
-import androidx.annotation.RequiresApi;
|
|
29
|
21
|
|
|
30
|
22
|
import com.facebook.react.bridge.Arguments;
|
|
31
|
23
|
import com.facebook.react.bridge.Promise;
|
|
|
@@ -61,9 +53,7 @@ import java.util.concurrent.Executors;
|
|
61
|
53
|
* {@code AudioModeModule.DEFAULT} mode should be used.
|
|
62
|
54
|
*/
|
|
63
|
55
|
@ReactModule(name = AudioModeModule.NAME)
|
|
64
|
|
-class AudioModeModule extends ReactContextBaseJavaModule
|
|
65
|
|
- implements AudioManager.OnAudioFocusChangeListener {
|
|
66
|
|
-
|
|
|
56
|
+class AudioModeModule extends ReactContextBaseJavaModule {
|
|
67
|
57
|
public static final String NAME = "AudioMode";
|
|
68
|
58
|
|
|
69
|
59
|
/**
|
|
|
@@ -75,24 +65,9 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
75
|
65
|
* - VIDEO_CALL: Used for video calls. It will use the speaker by default,
|
|
76
|
66
|
* unless a wired or Bluetooth headset is connected.
|
|
77
|
67
|
*/
|
|
78
|
|
- private static final int DEFAULT = 0;
|
|
79
|
|
- private static final int AUDIO_CALL = 1;
|
|
80
|
|
- private static final int VIDEO_CALL = 2;
|
|
81
|
|
-
|
|
82
|
|
- /**
|
|
83
|
|
- * Constant defining the action for plugging in a headset. This is used on
|
|
84
|
|
- * our device detection system for API < 23.
|
|
85
|
|
- */
|
|
86
|
|
- private static final String ACTION_HEADSET_PLUG
|
|
87
|
|
- = (Build.VERSION.SDK_INT >= 21)
|
|
88
|
|
- ? AudioManager.ACTION_HEADSET_PLUG
|
|
89
|
|
- : Intent.ACTION_HEADSET_PLUG;
|
|
90
|
|
-
|
|
91
|
|
- /**
|
|
92
|
|
- * Constant defining a USB headset. Only available on API level >= 26.
|
|
93
|
|
- * The value of: AudioDeviceInfo.TYPE_USB_HEADSET
|
|
94
|
|
- */
|
|
95
|
|
- private static final int TYPE_USB_HEADSET = 22;
|
|
|
68
|
+ static final int DEFAULT = 0;
|
|
|
69
|
+ static final int AUDIO_CALL = 1;
|
|
|
70
|
+ static final int VIDEO_CALL = 2;
|
|
96
|
71
|
|
|
97
|
72
|
/**
|
|
98
|
73
|
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
|
|
|
@@ -100,136 +75,23 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
100
|
75
|
static final String TAG = NAME;
|
|
101
|
76
|
|
|
102
|
77
|
/**
|
|
103
|
|
- * Converts any of the "DEVICE_" constants into the corresponding
|
|
104
|
|
- * {@link android.telecom.CallAudioState} "ROUTE_" number.
|
|
105
|
|
- *
|
|
106
|
|
- * @param audioDevice one of the "DEVICE_" constants.
|
|
107
|
|
- * @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
|
|
108
|
|
- * no match is found.
|
|
|
78
|
+ * Whether or not the ConnectionService is used for selecting audio devices.
|
|
109
|
79
|
*/
|
|
110
|
|
- @RequiresApi(api = Build.VERSION_CODES.M)
|
|
111
|
|
- private static int audioDeviceToRouteInt(String audioDevice) {
|
|
112
|
|
- if (audioDevice == null) {
|
|
113
|
|
- return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
|
114
|
|
- }
|
|
115
|
|
- switch (audioDevice) {
|
|
116
|
|
- case DEVICE_BLUETOOTH:
|
|
117
|
|
- return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
|
|
118
|
|
- case DEVICE_EARPIECE:
|
|
119
|
|
- return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
|
120
|
|
- case DEVICE_HEADPHONES:
|
|
121
|
|
- return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
|
|
122
|
|
- case DEVICE_SPEAKER:
|
|
123
|
|
- return android.telecom.CallAudioState.ROUTE_SPEAKER;
|
|
124
|
|
- default:
|
|
125
|
|
- JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
|
|
126
|
|
- return android.telecom.CallAudioState.ROUTE_EARPIECE;
|
|
127
|
|
- }
|
|
128
|
|
- }
|
|
129
|
80
|
|
|
130
|
|
- /**
|
|
131
|
|
- * Populates given route mask into the "DEVICE_" list.
|
|
132
|
|
- *
|
|
133
|
|
- * @param supportedRouteMask an integer coming from
|
|
134
|
|
- * {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
|
|
135
|
|
- * @return a list of device names.
|
|
136
|
|
- */
|
|
137
|
|
- @RequiresApi(api = Build.VERSION_CODES.M)
|
|
138
|
|
- private static Set<String> routesToDeviceNames(int supportedRouteMask) {
|
|
139
|
|
- Set<String> devices = new HashSet<>();
|
|
140
|
|
- if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
|
|
141
|
|
- == android.telecom.CallAudioState.ROUTE_EARPIECE) {
|
|
142
|
|
- devices.add(DEVICE_EARPIECE);
|
|
143
|
|
- }
|
|
144
|
|
- if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
|
|
145
|
|
- == android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
|
|
146
|
|
- devices.add(DEVICE_BLUETOOTH);
|
|
147
|
|
- }
|
|
148
|
|
- if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
|
|
149
|
|
- == android.telecom.CallAudioState.ROUTE_SPEAKER) {
|
|
150
|
|
- devices.add(DEVICE_SPEAKER);
|
|
151
|
|
- }
|
|
152
|
|
- if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
|
|
153
|
|
- == android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
|
|
154
|
|
- devices.add(DEVICE_HEADPHONES);
|
|
155
|
|
- }
|
|
156
|
|
- return devices;
|
|
157
|
|
- }
|
|
|
81
|
+ private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
|
|
82
|
+ private static boolean useConnectionService_ = supportsConnectionService;
|
|
158
|
83
|
|
|
159
|
|
- /**
|
|
160
|
|
- * Whether or not the ConnectionService is used for selecting audio devices.
|
|
161
|
|
- */
|
|
162
|
84
|
static boolean useConnectionService() {
|
|
163
|
|
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
|
|
85
|
+ return supportsConnectionService && useConnectionService_;
|
|
164
|
86
|
}
|
|
165
|
87
|
|
|
166
|
|
- /**
|
|
167
|
|
- * Indicator that we have lost audio focus.
|
|
168
|
|
- */
|
|
169
|
|
- private boolean audioFocusLost = false;
|
|
170
|
|
-
|
|
171
|
|
- /**
|
|
172
|
|
- * {@link AudioManager} instance used to interact with the Android audio
|
|
173
|
|
- * subsystem.
|
|
174
|
|
- */
|
|
175
|
|
- private final AudioManager audioManager;
|
|
176
|
|
-
|
|
177
|
|
- /**
|
|
178
|
|
- * {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
|
|
179
|
|
- * old (< M) Android versions.
|
|
180
|
|
- */
|
|
181
|
|
- private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
|
|
|
88
|
+ private AudioDeviceHandlerInterface audioDeviceHandler;
|
|
182
|
89
|
|
|
183
|
90
|
/**
|
|
184
|
91
|
* {@link ExecutorService} for running all audio operations on a dedicated
|
|
185
|
92
|
* thread.
|
|
186
|
93
|
*/
|
|
187
|
|
- private static final ExecutorService executor
|
|
188
|
|
- = Executors.newSingleThreadExecutor();
|
|
189
|
|
-
|
|
190
|
|
- /**
|
|
191
|
|
- * {@link Runnable} for running audio device detection the main thread.
|
|
192
|
|
- * This is only used on Android >= M.
|
|
193
|
|
- */
|
|
194
|
|
- private final Runnable onAudioDeviceChangeRunner = new Runnable() {
|
|
195
|
|
- @TargetApi(Build.VERSION_CODES.M)
|
|
196
|
|
- @Override
|
|
197
|
|
- public void run() {
|
|
198
|
|
- Set<String> devices = new HashSet<>();
|
|
199
|
|
- AudioDeviceInfo[] deviceInfos
|
|
200
|
|
- = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
|
|
201
|
|
-
|
|
202
|
|
- for (AudioDeviceInfo info: deviceInfos) {
|
|
203
|
|
- switch (info.getType()) {
|
|
204
|
|
- case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
|
|
205
|
|
- devices.add(DEVICE_BLUETOOTH);
|
|
206
|
|
- break;
|
|
207
|
|
- case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
|
|
208
|
|
- devices.add(DEVICE_EARPIECE);
|
|
209
|
|
- break;
|
|
210
|
|
- case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
|
|
211
|
|
- devices.add(DEVICE_SPEAKER);
|
|
212
|
|
- break;
|
|
213
|
|
- case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
|
|
214
|
|
- case AudioDeviceInfo.TYPE_WIRED_HEADSET:
|
|
215
|
|
- case TYPE_USB_HEADSET:
|
|
216
|
|
- devices.add(DEVICE_HEADPHONES);
|
|
217
|
|
- break;
|
|
218
|
|
- }
|
|
219
|
|
- }
|
|
220
|
|
-
|
|
221
|
|
- availableDevices = devices;
|
|
222
|
|
- JitsiMeetLogger.i(TAG + " Available audio devices: " +
|
|
223
|
|
- availableDevices.toString());
|
|
224
|
|
-
|
|
225
|
|
- // Reset user selection
|
|
226
|
|
- userSelectedDevice = null;
|
|
227
|
|
-
|
|
228
|
|
- if (mode != -1) {
|
|
229
|
|
- updateAudioRoute(mode);
|
|
230
|
|
- }
|
|
231
|
|
- }
|
|
232
|
|
- };
|
|
|
94
|
+ private static final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
233
|
95
|
|
|
234
|
96
|
/**
|
|
235
|
97
|
* Audio mode currently in use.
|
|
|
@@ -239,10 +101,10 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
239
|
101
|
/**
|
|
240
|
102
|
* Audio device types.
|
|
241
|
103
|
*/
|
|
242
|
|
- private static final String DEVICE_BLUETOOTH = "BLUETOOTH";
|
|
243
|
|
- private static final String DEVICE_EARPIECE = "EARPIECE";
|
|
244
|
|
- private static final String DEVICE_HEADPHONES = "HEADPHONES";
|
|
245
|
|
- private static final String DEVICE_SPEAKER = "SPEAKER";
|
|
|
104
|
+ static final String DEVICE_BLUETOOTH = "BLUETOOTH";
|
|
|
105
|
+ static final String DEVICE_EARPIECE = "EARPIECE";
|
|
|
106
|
+ static final String DEVICE_HEADPHONES = "HEADPHONES";
|
|
|
107
|
+ static final String DEVICE_SPEAKER = "SPEAKER";
|
|
246
|
108
|
|
|
247
|
109
|
/**
|
|
248
|
110
|
* Device change event.
|
|
|
@@ -259,15 +121,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
259
|
121
|
*/
|
|
260
|
122
|
private String selectedDevice;
|
|
261
|
123
|
|
|
262
|
|
- /**
|
|
263
|
|
- * Used on API >= 26 to store the most recently reported audio devices.
|
|
264
|
|
- * Makes it easier to compare for a change, because the devices are stored
|
|
265
|
|
- * as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
|
|
266
|
|
- * the {@link #availableDevices} on each update.
|
|
267
|
|
- */
|
|
268
|
|
- @RequiresApi(api = Build.VERSION_CODES.O)
|
|
269
|
|
- private int supportedRouteMask;
|
|
270
|
|
-
|
|
271
|
124
|
/**
|
|
272
|
125
|
* User selected device. When null the default is used depending on the
|
|
273
|
126
|
* mode.
|
|
|
@@ -284,30 +137,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
284
|
137
|
public AudioModeModule(ReactApplicationContext reactContext) {
|
|
285
|
138
|
super(reactContext);
|
|
286
|
139
|
|
|
287
|
|
- audioManager
|
|
288
|
|
- = (AudioManager)
|
|
289
|
|
- reactContext.getSystemService(Context.AUDIO_SERVICE);
|
|
290
|
|
-
|
|
291
|
|
- // Starting Oreo the ConnectionImpl from ConnectionService is used to
|
|
292
|
|
- // detect the available devices.
|
|
293
|
|
- if (!useConnectionService()) {
|
|
294
|
|
- // Setup runtime device change detection.
|
|
295
|
|
- setupAudioRouteChangeDetection();
|
|
296
|
|
-
|
|
297
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
298
|
|
- // Do an initial detection on Android >= M.
|
|
299
|
|
- onAudioDeviceChange();
|
|
300
|
|
- } else {
|
|
301
|
|
- // On Android < M, detect if we have an earpiece.
|
|
302
|
|
- PackageManager pm = reactContext.getPackageManager();
|
|
303
|
|
- if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
|
|
304
|
|
- availableDevices.add(DEVICE_EARPIECE);
|
|
305
|
|
- }
|
|
306
|
|
-
|
|
307
|
|
- // Always assume there is a speaker.
|
|
308
|
|
- availableDevices.add(DEVICE_SPEAKER);
|
|
309
|
|
- }
|
|
310
|
|
- }
|
|
|
140
|
+ setAudioDeviceHandler();
|
|
311
|
141
|
}
|
|
312
|
142
|
|
|
313
|
143
|
/**
|
|
|
@@ -363,133 +193,27 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
363
|
193
|
return NAME;
|
|
364
|
194
|
}
|
|
365
|
195
|
|
|
366
|
|
- /**
|
|
367
|
|
- * Helper method to trigger an audio route update when devices change. It
|
|
368
|
|
- * makes sure the operation is performed on the main thread.
|
|
369
|
|
- *
|
|
370
|
|
- * Only used on Android >= M.
|
|
371
|
|
- */
|
|
372
|
|
- void onAudioDeviceChange() {
|
|
373
|
|
- runInAudioThread(onAudioDeviceChangeRunner);
|
|
374
|
|
- }
|
|
375
|
|
-
|
|
376
|
|
- /**
|
|
377
|
|
- * Helper method to trigger an audio route update when Bluetooth devices are
|
|
378
|
|
- * connected / disconnected.
|
|
379
|
|
- *
|
|
380
|
|
- * Only used on Android < M. Runs on the main thread.
|
|
381
|
|
- */
|
|
382
|
|
- void onBluetoothDeviceChange() {
|
|
383
|
|
- if (bluetoothHeadsetMonitor != null && bluetoothHeadsetMonitor.isHeadsetAvailable()) {
|
|
384
|
|
- availableDevices.add(DEVICE_BLUETOOTH);
|
|
385
|
|
- } else {
|
|
386
|
|
- availableDevices.remove(DEVICE_BLUETOOTH);
|
|
|
196
|
+ private void setAudioDeviceHandler() {
|
|
|
197
|
+ if (audioDeviceHandler != null) {
|
|
|
198
|
+ audioDeviceHandler.stop();
|
|
387
|
199
|
}
|
|
388
|
200
|
|
|
389
|
|
- if (mode != -1) {
|
|
390
|
|
- updateAudioRoute(mode);
|
|
391
|
|
- }
|
|
392
|
|
- }
|
|
393
|
|
-
|
|
394
|
|
- /**
|
|
395
|
|
- * Helper method to trigger an audio route update when a headset is plugged
|
|
396
|
|
- * or unplugged.
|
|
397
|
|
- *
|
|
398
|
|
- * Only used on Android < M.
|
|
399
|
|
- */
|
|
400
|
|
- void onHeadsetDeviceChange() {
|
|
401
|
|
- runInAudioThread(new Runnable() {
|
|
402
|
|
- @Override
|
|
403
|
|
- public void run() {
|
|
404
|
|
- // XXX: isWiredHeadsetOn is not deprecated when used just for
|
|
405
|
|
- // knowing if there is a wired headset connected, regardless of
|
|
406
|
|
- // audio being routed to it.
|
|
407
|
|
- //noinspection deprecation
|
|
408
|
|
- if (audioManager.isWiredHeadsetOn()) {
|
|
409
|
|
- availableDevices.add(DEVICE_HEADPHONES);
|
|
410
|
|
- } else {
|
|
411
|
|
- availableDevices.remove(DEVICE_HEADPHONES);
|
|
412
|
|
- }
|
|
413
|
|
-
|
|
414
|
|
- if (mode != -1) {
|
|
415
|
|
- updateAudioRoute(mode);
|
|
416
|
|
- }
|
|
417
|
|
- }
|
|
418
|
|
- });
|
|
419
|
|
- }
|
|
420
|
|
-
|
|
421
|
|
- @RequiresApi(api = Build.VERSION_CODES.O)
|
|
422
|
|
- void onCallAudioStateChange(Object callAudioState_) {
|
|
423
|
|
- final android.telecom.CallAudioState callAudioState
|
|
424
|
|
- = (android.telecom.CallAudioState)callAudioState_;
|
|
425
|
|
- runInAudioThread(new Runnable() {
|
|
426
|
|
- @Override
|
|
427
|
|
- public void run() {
|
|
428
|
|
- int newSupportedRoutes = callAudioState.getSupportedRouteMask();
|
|
429
|
|
- boolean audioDevicesChanged
|
|
430
|
|
- = supportedRouteMask != newSupportedRoutes;
|
|
431
|
|
- if (audioDevicesChanged) {
|
|
432
|
|
- supportedRouteMask = newSupportedRoutes;
|
|
433
|
|
- availableDevices = routesToDeviceNames(supportedRouteMask);
|
|
434
|
|
- JitsiMeetLogger.i(TAG + " Available audio devices: "
|
|
435
|
|
- + availableDevices.toString());
|
|
436
|
|
- }
|
|
437
|
|
-
|
|
438
|
|
- boolean audioRouteChanged
|
|
439
|
|
- = audioDeviceToRouteInt(selectedDevice)
|
|
440
|
|
- != callAudioState.getRoute();
|
|
441
|
|
-
|
|
442
|
|
- if (audioRouteChanged || audioDevicesChanged) {
|
|
443
|
|
- // Reset user selection
|
|
444
|
|
- userSelectedDevice = null;
|
|
445
|
|
-
|
|
446
|
|
- // If the OS changes the Audio Route or Devices we could have lost
|
|
447
|
|
- // the selected audio device
|
|
448
|
|
- selectedDevice = null;
|
|
449
|
|
-
|
|
450
|
|
- if (mode != -1) {
|
|
451
|
|
- updateAudioRoute(mode);
|
|
452
|
|
- }
|
|
453
|
|
- }
|
|
454
|
|
- }
|
|
455
|
|
- });
|
|
456
|
|
- }
|
|
457
|
|
-
|
|
458
|
|
- /**
|
|
459
|
|
- * {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
|
|
460
|
|
- * when the audio focus of the system is updated.
|
|
461
|
|
- *
|
|
462
|
|
- * @param focusChange - The type of focus change.
|
|
463
|
|
- */
|
|
464
|
|
- @Override
|
|
465
|
|
- public void onAudioFocusChange(int focusChange) {
|
|
466
|
|
- switch (focusChange) {
|
|
467
|
|
- case AudioManager.AUDIOFOCUS_GAIN: {
|
|
468
|
|
- JitsiMeetLogger.d(TAG + " Audio focus gained");
|
|
469
|
|
- // Some other application potentially stole our audio focus
|
|
470
|
|
- // temporarily. Restore our mode.
|
|
471
|
|
- if (audioFocusLost) {
|
|
472
|
|
- updateAudioRoute(mode);
|
|
473
|
|
- }
|
|
474
|
|
- audioFocusLost = false;
|
|
475
|
|
- break;
|
|
476
|
|
- }
|
|
477
|
|
- case AudioManager.AUDIOFOCUS_LOSS:
|
|
478
|
|
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
479
|
|
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
|
|
480
|
|
- JitsiMeetLogger.d(TAG + " Audio focus lost");
|
|
481
|
|
- audioFocusLost = true;
|
|
482
|
|
- break;
|
|
|
201
|
+ if (useConnectionService()) {
|
|
|
202
|
+ audioDeviceHandler = new AudioDeviceHandlerConnectionService();
|
|
|
203
|
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
204
|
+ audioDeviceHandler = new AudioDeviceHandlerGeneric();
|
|
|
205
|
+ } else {
|
|
|
206
|
+ audioDeviceHandler = new AudioDeviceHandlerLegacy();
|
|
483
|
207
|
}
|
|
484
|
208
|
|
|
485
|
|
- }
|
|
|
209
|
+ audioDeviceHandler.start(getReactApplicationContext(), this);
|
|
486
|
210
|
}
|
|
487
|
211
|
|
|
488
|
212
|
/**
|
|
489
|
213
|
* Helper function to run operations on a dedicated thread.
|
|
490
|
214
|
* @param runnable
|
|
491
|
215
|
*/
|
|
492
|
|
- public void runInAudioThread(Runnable runnable) {
|
|
|
216
|
+ void runInAudioThread(Runnable runnable) {
|
|
493
|
217
|
executor.execute(runnable);
|
|
494
|
218
|
}
|
|
495
|
219
|
|
|
|
@@ -518,46 +242,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
518
|
242
|
});
|
|
519
|
243
|
}
|
|
520
|
244
|
|
|
521
|
|
- /**
|
|
522
|
|
- * The API >= 26 way of adjusting the audio route.
|
|
523
|
|
- *
|
|
524
|
|
- * @param audioDevice one of the "DEVICE_" names to set as the audio route.
|
|
525
|
|
- */
|
|
526
|
|
- @RequiresApi(api = Build.VERSION_CODES.O)
|
|
527
|
|
- private void setAudioRoute(String audioDevice) {
|
|
528
|
|
- int newAudioRoute = audioDeviceToRouteInt(audioDevice);
|
|
529
|
|
-
|
|
530
|
|
- RNConnectionService.setAudioRoute(newAudioRoute);
|
|
531
|
|
- }
|
|
532
|
|
-
|
|
533
|
|
- /**
|
|
534
|
|
- * The API < 26 way of adjusting the audio route.
|
|
535
|
|
- *
|
|
536
|
|
- * @param audioDevice one of the "DEVICE_" names to set as the audio route.
|
|
537
|
|
- */
|
|
538
|
|
- private void setAudioRoutePreO(String audioDevice) {
|
|
539
|
|
- // Turn bluetooth on / off
|
|
540
|
|
- setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
|
|
541
|
|
-
|
|
542
|
|
- // Turn speaker on / off
|
|
543
|
|
- audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
|
|
544
|
|
- }
|
|
545
|
|
-
|
|
546
|
|
- /**
|
|
547
|
|
- * Helper method to set the output route to a Bluetooth device.
|
|
548
|
|
- *
|
|
549
|
|
- * @param enabled true if Bluetooth should use used, false otherwise.
|
|
550
|
|
- */
|
|
551
|
|
- private void setBluetoothAudioRoute(boolean enabled) {
|
|
552
|
|
- if (enabled) {
|
|
553
|
|
- audioManager.startBluetoothSco();
|
|
554
|
|
- audioManager.setBluetoothScoOn(true);
|
|
555
|
|
- } else {
|
|
556
|
|
- audioManager.setBluetoothScoOn(false);
|
|
557
|
|
- audioManager.stopBluetoothSco();
|
|
558
|
|
- }
|
|
559
|
|
- }
|
|
560
|
|
-
|
|
561
|
245
|
/**
|
|
562
|
246
|
* Public method to set the current audio mode.
|
|
563
|
247
|
*
|
|
|
@@ -587,70 +271,22 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
587
|
271
|
AudioModeModule.this.mode = mode;
|
|
588
|
272
|
promise.resolve(null);
|
|
589
|
273
|
} else {
|
|
590
|
|
- promise.reject(
|
|
591
|
|
- "setMode",
|
|
592
|
|
- "Failed to set audio mode to " + mode);
|
|
|
274
|
+ promise.reject("setMode", "Failed to set audio mode to " + mode);
|
|
593
|
275
|
}
|
|
594
|
276
|
}
|
|
595
|
277
|
});
|
|
596
|
278
|
}
|
|
597
|
279
|
|
|
598
|
280
|
/**
|
|
599
|
|
- * Setup the audio route change detection mechanism. We use the
|
|
600
|
|
- * {@link android.media.AudioDeviceCallback} on 23 >= Android API < 26.
|
|
601
|
|
- */
|
|
602
|
|
- private void setupAudioRouteChangeDetection() {
|
|
603
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
604
|
|
- setupAudioRouteChangeDetectionM();
|
|
605
|
|
- } else {
|
|
606
|
|
- setupAudioRouteChangeDetectionPreM();
|
|
607
|
|
- }
|
|
608
|
|
- }
|
|
609
|
|
-
|
|
610
|
|
- /**
|
|
611
|
|
- * Audio route change detection mechanism for 23 >= Android API < 26.
|
|
612
|
|
- */
|
|
613
|
|
- @TargetApi(Build.VERSION_CODES.M)
|
|
614
|
|
- private void setupAudioRouteChangeDetectionM() {
|
|
615
|
|
- android.media.AudioDeviceCallback audioDeviceCallback =
|
|
616
|
|
- new android.media.AudioDeviceCallback() {
|
|
617
|
|
- @Override
|
|
618
|
|
- public void onAudioDevicesAdded(
|
|
619
|
|
- AudioDeviceInfo[] addedDevices) {
|
|
620
|
|
- JitsiMeetLogger.d(TAG + " Audio devices added");
|
|
621
|
|
- onAudioDeviceChange();
|
|
622
|
|
- }
|
|
623
|
|
-
|
|
624
|
|
- @Override
|
|
625
|
|
- public void onAudioDevicesRemoved(
|
|
626
|
|
- AudioDeviceInfo[] removedDevices) {
|
|
627
|
|
- JitsiMeetLogger.d(TAG + " Audio devices removed");
|
|
628
|
|
- onAudioDeviceChange();
|
|
629
|
|
- }
|
|
630
|
|
- };
|
|
631
|
|
-
|
|
632
|
|
- audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
|
|
633
|
|
- }
|
|
634
|
|
-
|
|
635
|
|
- /**
|
|
636
|
|
- * Audio route change detection mechanism for Android API < 23.
|
|
|
281
|
+ * Sets whether ConnectionService should be used (if available) for setting the audio mode
|
|
|
282
|
+ * or not.
|
|
|
283
|
+ *
|
|
|
284
|
+ * @param use Boolean indicator of where it should be used or not.
|
|
637
|
285
|
*/
|
|
638
|
|
- private void setupAudioRouteChangeDetectionPreM() {
|
|
639
|
|
- Context context = getReactApplicationContext();
|
|
640
|
|
-
|
|
641
|
|
- // Detect changes in wired headset connections.
|
|
642
|
|
- IntentFilter wiredHeadSetFilter = new IntentFilter(ACTION_HEADSET_PLUG);
|
|
643
|
|
- BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
|
|
644
|
|
- @Override
|
|
645
|
|
- public void onReceive(Context context, Intent intent) {
|
|
646
|
|
- JitsiMeetLogger.d(TAG + " Wired headset added / removed");
|
|
647
|
|
- onHeadsetDeviceChange();
|
|
648
|
|
- }
|
|
649
|
|
- };
|
|
650
|
|
- context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
|
|
651
|
|
-
|
|
652
|
|
- // Detect Bluetooth device changes.
|
|
653
|
|
- bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(this, context);
|
|
|
286
|
+ @ReactMethod
|
|
|
287
|
+ public void setUseConnectionService(boolean use) {
|
|
|
288
|
+ useConnectionService_ = use;
|
|
|
289
|
+ setAudioDeviceHandler();
|
|
654
|
290
|
}
|
|
655
|
291
|
|
|
656
|
292
|
/**
|
|
|
@@ -663,14 +299,11 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
663
|
299
|
private boolean updateAudioRoute(int mode) {
|
|
664
|
300
|
JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
|
|
665
|
301
|
|
|
|
302
|
+ if (!audioDeviceHandler.setMode(mode)) {
|
|
|
303
|
+ return false;
|
|
|
304
|
+ }
|
|
|
305
|
+
|
|
666
|
306
|
if (mode == DEFAULT) {
|
|
667
|
|
- if (!useConnectionService()) {
|
|
668
|
|
- audioFocusLost = false;
|
|
669
|
|
- audioManager.setMode(AudioManager.MODE_NORMAL);
|
|
670
|
|
- audioManager.abandonAudioFocus(this);
|
|
671
|
|
- audioManager.setSpeakerphoneOn(false);
|
|
672
|
|
- setBluetoothAudioRoute(false);
|
|
673
|
|
- }
|
|
674
|
307
|
selectedDevice = null;
|
|
675
|
308
|
userSelectedDevice = null;
|
|
676
|
309
|
|
|
|
@@ -678,20 +311,6 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
678
|
311
|
return true;
|
|
679
|
312
|
}
|
|
680
|
313
|
|
|
681
|
|
- if (!useConnectionService()) {
|
|
682
|
|
- audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
683
|
|
- audioManager.setMicrophoneMute(false);
|
|
684
|
|
-
|
|
685
|
|
- if (audioManager.requestAudioFocus(
|
|
686
|
|
- this,
|
|
687
|
|
- AudioManager.STREAM_VOICE_CALL,
|
|
688
|
|
- AudioManager.AUDIOFOCUS_GAIN)
|
|
689
|
|
- == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
|
690
|
|
- JitsiMeetLogger.w(TAG + " Audio focus request failed");
|
|
691
|
|
- return false;
|
|
692
|
|
- }
|
|
693
|
|
- }
|
|
694
|
|
-
|
|
695
|
314
|
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
|
|
696
|
315
|
boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
|
|
697
|
316
|
|
|
|
@@ -706,8 +325,7 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
706
|
325
|
}
|
|
707
|
326
|
|
|
708
|
327
|
// Consider the user's selection
|
|
709
|
|
- if (userSelectedDevice != null
|
|
710
|
|
- && availableDevices.contains(userSelectedDevice)) {
|
|
|
328
|
+ if (userSelectedDevice != null && availableDevices.contains(userSelectedDevice)) {
|
|
711
|
329
|
audioDevice = userSelectedDevice;
|
|
712
|
330
|
}
|
|
713
|
331
|
|
|
|
@@ -720,13 +338,97 @@ class AudioModeModule extends ReactContextBaseJavaModule
|
|
720
|
338
|
selectedDevice = audioDevice;
|
|
721
|
339
|
JitsiMeetLogger.i(TAG + " Selected audio device: " + audioDevice);
|
|
722
|
340
|
|
|
723
|
|
- if (useConnectionService()) {
|
|
724
|
|
- setAudioRoute(audioDevice);
|
|
725
|
|
- } else {
|
|
726
|
|
- setAudioRoutePreO(audioDevice);
|
|
727
|
|
- }
|
|
|
341
|
+ audioDeviceHandler.setAudioRoute(audioDevice);
|
|
728
|
342
|
|
|
729
|
343
|
notifyDevicesChanged();
|
|
730
|
344
|
return true;
|
|
731
|
345
|
}
|
|
|
346
|
+
|
|
|
347
|
+ /**
|
|
|
348
|
+ * Gets the currently selected audio device.
|
|
|
349
|
+ *
|
|
|
350
|
+ * @return The selected audio device.
|
|
|
351
|
+ */
|
|
|
352
|
+ String getSelectedDevice() {
|
|
|
353
|
+ return selectedDevice;
|
|
|
354
|
+ }
|
|
|
355
|
+
|
|
|
356
|
+ /**
|
|
|
357
|
+ * Resets the current device selection.
|
|
|
358
|
+ */
|
|
|
359
|
+ void resetSelectedDevice() {
|
|
|
360
|
+ selectedDevice = null;
|
|
|
361
|
+ userSelectedDevice = null;
|
|
|
362
|
+ }
|
|
|
363
|
+
|
|
|
364
|
+ /**
|
|
|
365
|
+ * Adds a new device to the list of available devices.
|
|
|
366
|
+ *
|
|
|
367
|
+ * @param device The new device.
|
|
|
368
|
+ */
|
|
|
369
|
+ void addDevice(String device) {
|
|
|
370
|
+ availableDevices.add(device);
|
|
|
371
|
+ resetSelectedDevice();
|
|
|
372
|
+ }
|
|
|
373
|
+
|
|
|
374
|
+ /**
|
|
|
375
|
+ * Removes a device from the list of available devices.
|
|
|
376
|
+ *
|
|
|
377
|
+ * @param device The old device to the removed.
|
|
|
378
|
+ */
|
|
|
379
|
+ void removeDevice(String device) {
|
|
|
380
|
+ availableDevices.remove(device);
|
|
|
381
|
+ resetSelectedDevice();
|
|
|
382
|
+ }
|
|
|
383
|
+
|
|
|
384
|
+ /**
|
|
|
385
|
+ * Replaces the current list of available devices with a new one.
|
|
|
386
|
+ *
|
|
|
387
|
+ * @param devices The new devices list.
|
|
|
388
|
+ */
|
|
|
389
|
+ void replaceDevices(Set<String> devices) {
|
|
|
390
|
+ availableDevices = devices;
|
|
|
391
|
+ resetSelectedDevice();
|
|
|
392
|
+ }
|
|
|
393
|
+
|
|
|
394
|
+ /**
|
|
|
395
|
+ * Re-sets the current audio route. Needed when devices changes have happened.
|
|
|
396
|
+ */
|
|
|
397
|
+ void updateAudioRoute() {
|
|
|
398
|
+ if (mode != -1) {
|
|
|
399
|
+ updateAudioRoute(mode);
|
|
|
400
|
+ }
|
|
|
401
|
+ }
|
|
|
402
|
+
|
|
|
403
|
+ /**
|
|
|
404
|
+ * Interface for the modules implementing the actual audio device management.
|
|
|
405
|
+ */
|
|
|
406
|
+ interface AudioDeviceHandlerInterface {
|
|
|
407
|
+ /**
|
|
|
408
|
+ * Start detecting audio device changes.
|
|
|
409
|
+ * @param context Android {@link Context} where detection should take place.
|
|
|
410
|
+ * @param audioModeModule Reference to the main {@link AudioModeModule}.
|
|
|
411
|
+ */
|
|
|
412
|
+ void start(Context context, AudioModeModule audioModeModule);
|
|
|
413
|
+
|
|
|
414
|
+ /**
|
|
|
415
|
+ * Stop audio device detection.
|
|
|
416
|
+ */
|
|
|
417
|
+ void stop();
|
|
|
418
|
+
|
|
|
419
|
+ /**
|
|
|
420
|
+ * Set the appropriate route for the given audio device.
|
|
|
421
|
+ *
|
|
|
422
|
+ * @param device Audio device for which the route must be set.
|
|
|
423
|
+ */
|
|
|
424
|
+ void setAudioRoute(String device);
|
|
|
425
|
+
|
|
|
426
|
+ /**
|
|
|
427
|
+ * Set the given audio mode.
|
|
|
428
|
+ *
|
|
|
429
|
+ * @param mode The new audio mode to be used.
|
|
|
430
|
+ * @return Whether the operation was successful or not.
|
|
|
431
|
+ */
|
|
|
432
|
+ boolean setMode(int mode);
|
|
|
433
|
+ }
|
|
732
|
434
|
}
|