Parcourir la source

rn,android: refactor audio device handling module

Separate each implementation (3 as of this writing) into each own "handler"
class.

This should make the code easier to understand, maintain and extend.
master
Saúl Ibarra Corretgé il y a 6 ans
Parent
révision
3c22cd8ef4

+ 164
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerConnectionService.java Voir le fichier

1
+/*
2
+ * Copyright @ 2017-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk;
18
+
19
+import android.content.Context;
20
+import android.os.Build;
21
+import androidx.annotation.RequiresApi;
22
+
23
+import java.util.HashSet;
24
+import java.util.Set;
25
+
26
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
27
+
28
+
29
+/**
30
+ * {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
31
+ * Android versions >= O when ConnectionService is enabled.
32
+ */
33
+@RequiresApi(Build.VERSION_CODES.O)
34
+class AudioDeviceHandlerConnectionService implements
35
+        AudioModeModule.AudioDeviceHandlerInterface,
36
+        RNConnectionService.CallAudioStateListener {
37
+
38
+    private final static String TAG = AudioDeviceHandlerConnectionService.class.getSimpleName();
39
+
40
+    /**
41
+     * Reference to the main {@code AudioModeModule}.
42
+     */
43
+    private AudioModeModule module;
44
+
45
+    /**
46
+     * Converts any of the "DEVICE_" constants into the corresponding
47
+     * {@link android.telecom.CallAudioState} "ROUTE_" number.
48
+     *
49
+     * @param audioDevice one of the "DEVICE_" constants.
50
+     * @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
51
+     * no match is found.
52
+     */
53
+    private static int audioDeviceToRouteInt(String audioDevice) {
54
+        if (audioDevice == null) {
55
+            return android.telecom.CallAudioState.ROUTE_EARPIECE;
56
+        }
57
+        switch (audioDevice) {
58
+            case AudioModeModule.DEVICE_BLUETOOTH:
59
+                return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
60
+            case AudioModeModule.DEVICE_EARPIECE:
61
+                return android.telecom.CallAudioState.ROUTE_EARPIECE;
62
+            case AudioModeModule.DEVICE_HEADPHONES:
63
+                return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
64
+            case AudioModeModule.DEVICE_SPEAKER:
65
+                return android.telecom.CallAudioState.ROUTE_SPEAKER;
66
+            default:
67
+                JitsiMeetLogger.e(TAG + " Unsupported device name: " + audioDevice);
68
+                return android.telecom.CallAudioState.ROUTE_EARPIECE;
69
+        }
70
+    }
71
+
72
+    /**
73
+     * Populates given route mask into the "DEVICE_" list.
74
+     *
75
+     * @param supportedRouteMask an integer coming from
76
+     * {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
77
+     * @return a list of device names.
78
+     */
79
+    private static Set<String> routesToDeviceNames(int supportedRouteMask) {
80
+        Set<String> devices = new HashSet<>();
81
+        if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
82
+                == android.telecom.CallAudioState.ROUTE_EARPIECE) {
83
+            devices.add(AudioModeModule.DEVICE_EARPIECE);
84
+        }
85
+        if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
86
+                == android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
87
+            devices.add(AudioModeModule.DEVICE_BLUETOOTH);
88
+        }
89
+        if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
90
+                == android.telecom.CallAudioState.ROUTE_SPEAKER) {
91
+            devices.add(AudioModeModule.DEVICE_SPEAKER);
92
+        }
93
+        if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
94
+                == android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
95
+            devices.add(AudioModeModule.DEVICE_HEADPHONES);
96
+        }
97
+        return devices;
98
+    }
99
+
100
+    /**
101
+     * Used to store the most recently reported audio devices.
102
+     * Makes it easier to compare for a change, because the devices are stored
103
+     * as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
104
+     * the {@code availableDevices} on each update.
105
+     */
106
+    private int supportedRouteMask = -1;
107
+
108
+    public AudioDeviceHandlerConnectionService() {
109
+    }
110
+
111
+    @Override
112
+    public void onCallAudioStateChange(final android.telecom.CallAudioState callAudioState) {
113
+        module.runInAudioThread(new Runnable() {
114
+            @Override
115
+            public void run() {
116
+                boolean audioRouteChanged
117
+                    = audioDeviceToRouteInt(module.getSelectedDevice()) != callAudioState.getRoute();
118
+                int newSupportedRoutes = callAudioState.getSupportedRouteMask();
119
+                boolean audioDevicesChanged = supportedRouteMask != newSupportedRoutes;
120
+                if (audioDevicesChanged) {
121
+                    supportedRouteMask = newSupportedRoutes;
122
+                    Set<String> devices = routesToDeviceNames(supportedRouteMask);
123
+                    module.replaceDevices(devices);
124
+                    JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
125
+                }
126
+
127
+                if (audioRouteChanged || audioDevicesChanged) {
128
+                    module.resetSelectedDevice();
129
+                    module.updateAudioRoute();
130
+                }
131
+            }
132
+        });
133
+    }
134
+
135
+    @Override
136
+    public void start(Context context, AudioModeModule audioModeModule) {
137
+        JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
138
+
139
+        module = audioModeModule;
140
+        RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
141
+        if (rcs != null) {
142
+            rcs.setCallAudioStateListener(this);
143
+        }
144
+    }
145
+
146
+    @Override
147
+    public void stop() {
148
+        RNConnectionService rcs = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
149
+        if (rcs != null) {
150
+            rcs.setCallAudioStateListener(null);
151
+        }
152
+    }
153
+
154
+    public void setAudioRoute(String audioDevice) {
155
+        int newAudioRoute = audioDeviceToRouteInt(audioDevice);
156
+
157
+        RNConnectionService.setAudioRoute(newAudioRoute);
158
+    }
159
+
160
+    @Override
161
+    public boolean setMode(int mode) {
162
+        return true;
163
+    }
164
+}

+ 231
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerGeneric.java Voir le fichier

1
+/*
2
+ * Copyright @ 2017-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk;
18
+
19
+import android.content.Context;
20
+import android.media.AudioDeviceInfo;
21
+import android.media.AudioManager;
22
+import android.os.Build;
23
+import androidx.annotation.RequiresApi;
24
+
25
+import java.util.HashSet;
26
+import java.util.Set;
27
+
28
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
29
+
30
+
31
+/**
32
+ * {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
33
+ * all post-M Android versions. This handler can be used on any Android versions >= M, but by
34
+ * default it's only used on versions < O, since versions >= O use ConnectionService, but it
35
+ * can be disabled.
36
+ */
37
+@RequiresApi(Build.VERSION_CODES.M)
38
+class AudioDeviceHandlerGeneric implements
39
+        AudioModeModule.AudioDeviceHandlerInterface,
40
+        AudioManager.OnAudioFocusChangeListener {
41
+
42
+    private final static String TAG = AudioDeviceHandlerGeneric.class.getSimpleName();
43
+
44
+    /**
45
+     * Reference to the main {@code AudioModeModule}.
46
+     */
47
+    private AudioModeModule module;
48
+
49
+    /**
50
+     * Constant defining a USB headset. Only available on API level >= 26.
51
+     * The value of: AudioDeviceInfo.TYPE_USB_HEADSET
52
+     */
53
+    private static final int TYPE_USB_HEADSET = 22;
54
+
55
+    /**
56
+     * Indicator that we have lost audio focus.
57
+     */
58
+    private boolean audioFocusLost = false;
59
+
60
+    /**
61
+     * {@link AudioManager} instance used to interact with the Android audio
62
+     * subsystem.
63
+     */
64
+    private AudioManager audioManager;
65
+
66
+    /**
67
+     * {@link Runnable} for running audio device detection the main thread.
68
+     * This is only used on Android >= M.
69
+     */
70
+    private final Runnable onAudioDeviceChangeRunner = new Runnable() {
71
+        @Override
72
+        public void run() {
73
+            Set<String> devices = new HashSet<>();
74
+            AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
75
+
76
+            for (AudioDeviceInfo info: deviceInfos) {
77
+                switch (info.getType()) {
78
+                    case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
79
+                        devices.add(AudioModeModule.DEVICE_BLUETOOTH);
80
+                        break;
81
+                    case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
82
+                        devices.add(AudioModeModule.DEVICE_EARPIECE);
83
+                        break;
84
+                    case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
85
+                        devices.add(AudioModeModule.DEVICE_SPEAKER);
86
+                        break;
87
+                    case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
88
+                    case AudioDeviceInfo.TYPE_WIRED_HEADSET:
89
+                    case TYPE_USB_HEADSET:
90
+                        devices.add(AudioModeModule.DEVICE_HEADPHONES);
91
+                        break;
92
+                }
93
+            }
94
+
95
+            module.replaceDevices(devices);
96
+
97
+            JitsiMeetLogger.i(TAG + " Available audio devices: " + devices.toString());
98
+
99
+            module.updateAudioRoute();
100
+        }
101
+    };
102
+
103
+    private final android.media.AudioDeviceCallback audioDeviceCallback =
104
+        new android.media.AudioDeviceCallback() {
105
+            @Override
106
+            public void onAudioDevicesAdded(
107
+                AudioDeviceInfo[] addedDevices) {
108
+                JitsiMeetLogger.d(TAG + " Audio devices added");
109
+                onAudioDeviceChange();
110
+            }
111
+
112
+            @Override
113
+            public void onAudioDevicesRemoved(
114
+                AudioDeviceInfo[] removedDevices) {
115
+                JitsiMeetLogger.d(TAG + " Audio devices removed");
116
+                onAudioDeviceChange();
117
+            }
118
+        };
119
+
120
+    public AudioDeviceHandlerGeneric() {
121
+    }
122
+
123
+    /**
124
+     * Helper method to trigger an audio route update when devices change. It
125
+     * makes sure the operation is performed on the audio thread.
126
+     */
127
+    private void onAudioDeviceChange() {
128
+        module.runInAudioThread(onAudioDeviceChangeRunner);
129
+    }
130
+
131
+    /**
132
+     * {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
133
+     * when the audio focus of the system is updated.
134
+     *
135
+     * @param focusChange - The type of focus change.
136
+     */
137
+    @Override
138
+    public void onAudioFocusChange(final int focusChange) {
139
+        module.runInAudioThread(new Runnable() {
140
+            @Override
141
+            public void run() {
142
+                switch (focusChange) {
143
+                    case AudioManager.AUDIOFOCUS_GAIN: {
144
+                        JitsiMeetLogger.d(TAG + " Audio focus gained");
145
+                        // Some other application potentially stole our audio focus
146
+                        // temporarily. Restore our mode.
147
+                        if (audioFocusLost) {
148
+                            module.updateAudioRoute();
149
+                        }
150
+                        audioFocusLost = false;
151
+                        break;
152
+                    }
153
+                    case AudioManager.AUDIOFOCUS_LOSS:
154
+                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
155
+                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
156
+                        JitsiMeetLogger.d(TAG + " Audio focus lost");
157
+                        audioFocusLost = true;
158
+                        break;
159
+                    }
160
+                }
161
+            }
162
+        });
163
+    }
164
+
165
+    /**
166
+     * Helper method to set the output route to a Bluetooth device.
167
+     *
168
+     * @param enabled true if Bluetooth should use used, false otherwise.
169
+     */
170
+    private void setBluetoothAudioRoute(boolean enabled) {
171
+        if (enabled) {
172
+            audioManager.startBluetoothSco();
173
+            audioManager.setBluetoothScoOn(true);
174
+        } else {
175
+            audioManager.setBluetoothScoOn(false);
176
+            audioManager.stopBluetoothSco();
177
+        }
178
+    }
179
+
180
+    @Override
181
+    public void start(Context context, AudioModeModule audioModeModule) {
182
+        JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
183
+
184
+        module = audioModeModule;
185
+        audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
186
+
187
+        // Setup runtime device change detection.
188
+        audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
189
+
190
+        // Do an initial detection.
191
+        onAudioDeviceChange();
192
+    }
193
+
194
+    @Override
195
+    public void stop() {
196
+        audioManager.unregisterAudioDeviceCallback(audioDeviceCallback);
197
+    }
198
+
199
+    @Override
200
+    public void setAudioRoute(String device) {
201
+        // Turn bluetooth on / off
202
+        setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
203
+
204
+        // Turn speaker on / off
205
+        audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
206
+    }
207
+
208
+    @Override
209
+    public boolean setMode(int mode) {
210
+        if (mode == AudioModeModule.DEFAULT) {
211
+            audioFocusLost = false;
212
+            audioManager.setMode(AudioManager.MODE_NORMAL);
213
+            audioManager.abandonAudioFocus(this);
214
+            audioManager.setSpeakerphoneOn(false);
215
+            setBluetoothAudioRoute(false);
216
+
217
+            return true;
218
+        }
219
+
220
+        audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
221
+        audioManager.setMicrophoneMute(false);
222
+
223
+        if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
224
+            == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
225
+            JitsiMeetLogger.w(TAG + " Audio focus request failed");
226
+            return false;
227
+        }
228
+
229
+        return true;
230
+    }
231
+}

+ 229
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerLegacy.java Voir le fichier

1
+/*
2
+ * Copyright @ 2017-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk;
18
+
19
+import android.content.BroadcastReceiver;
20
+import android.content.Context;
21
+import android.content.Intent;
22
+import android.content.IntentFilter;
23
+import android.content.pm.PackageManager;
24
+import android.media.AudioManager;
25
+
26
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
27
+
28
+
29
+/**
30
+ * {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
31
+ * legacy (pre-M) Android versions.
32
+ */
33
+class AudioDeviceHandlerLegacy implements
34
+        AudioModeModule.AudioDeviceHandlerInterface,
35
+        AudioManager.OnAudioFocusChangeListener,
36
+        BluetoothHeadsetMonitor.Listener {
37
+
38
+    private final static String TAG = AudioDeviceHandlerLegacy.class.getSimpleName();
39
+
40
+    /**
41
+     * Reference to the main {@code AudioModeModule}.
42
+     */
43
+    private AudioModeModule module;
44
+
45
+    /**
46
+     * Indicator that we have lost audio focus.
47
+     */
48
+    private boolean audioFocusLost = false;
49
+
50
+    /**
51
+     * {@link AudioManager} instance used to interact with the Android audio
52
+     * subsystem.
53
+     */
54
+    private AudioManager audioManager;
55
+
56
+    /**
57
+     * {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in
58
+     * old (< M) Android versions.
59
+     */
60
+    private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
61
+
62
+    public AudioDeviceHandlerLegacy() {
63
+    }
64
+
65
+    /**
66
+     * Helper method to trigger an audio route update when Bluetooth devices are
67
+     * connected / disconnected.
68
+     */
69
+    @Override
70
+    public void onBluetoothDeviceChange(final boolean deviceAvailable) {
71
+        module.runInAudioThread(new Runnable() {
72
+            @Override
73
+            public void run() {
74
+                if (deviceAvailable) {
75
+                    module.addDevice(AudioModeModule.DEVICE_BLUETOOTH);
76
+                } else {
77
+                    module.removeDevice(AudioModeModule.DEVICE_BLUETOOTH);
78
+                }
79
+
80
+                module.updateAudioRoute();
81
+            }
82
+        });
83
+    }
84
+
85
+    /**
86
+     * Helper method to trigger an audio route update when a headset is plugged
87
+     * or unplugged.
88
+     */
89
+    private void onHeadsetDeviceChange() {
90
+        module.runInAudioThread(new Runnable() {
91
+            @Override
92
+            public void run() {
93
+                // XXX: isWiredHeadsetOn is not deprecated when used just for
94
+                // knowing if there is a wired headset connected, regardless of
95
+                // audio being routed to it.
96
+                //noinspection deprecation
97
+                if (audioManager.isWiredHeadsetOn()) {
98
+                    module.addDevice(AudioModeModule.DEVICE_HEADPHONES);
99
+                } else {
100
+                    module.removeDevice(AudioModeModule.DEVICE_HEADPHONES);
101
+                }
102
+
103
+                module.updateAudioRoute();
104
+            }
105
+        });
106
+    }
107
+
108
+    /**
109
+     * {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
110
+     * when the audio focus of the system is updated.
111
+     *
112
+     * @param focusChange - The type of focus change.
113
+     */
114
+    @Override
115
+    public void onAudioFocusChange(final int focusChange) {
116
+        module.runInAudioThread(new Runnable() {
117
+            @Override
118
+            public void run() {
119
+                switch (focusChange) {
120
+                    case AudioManager.AUDIOFOCUS_GAIN: {
121
+                        JitsiMeetLogger.d(TAG + " Audio focus gained");
122
+                        // Some other application potentially stole our audio focus
123
+                        // temporarily. Restore our mode.
124
+                        if (audioFocusLost) {
125
+                            module.updateAudioRoute();
126
+                        }
127
+                        audioFocusLost = false;
128
+                        break;
129
+                    }
130
+                    case AudioManager.AUDIOFOCUS_LOSS:
131
+                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
132
+                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
133
+                        JitsiMeetLogger.d(TAG + " Audio focus lost");
134
+                        audioFocusLost = true;
135
+                        break;
136
+                    }
137
+                }
138
+            }
139
+        });
140
+    }
141
+
142
+    /**
143
+     * Helper method to set the output route to a Bluetooth device.
144
+     *
145
+     * @param enabled true if Bluetooth should use used, false otherwise.
146
+     */
147
+    private void setBluetoothAudioRoute(boolean enabled) {
148
+        if (enabled) {
149
+            audioManager.startBluetoothSco();
150
+            audioManager.setBluetoothScoOn(true);
151
+        } else {
152
+            audioManager.setBluetoothScoOn(false);
153
+            audioManager.stopBluetoothSco();
154
+        }
155
+    }
156
+
157
+    @Override
158
+    public void start(Context context, AudioModeModule audioModeModule) {
159
+        JitsiMeetLogger.i("Using " + TAG + " as the audio device handler");
160
+
161
+        module = audioModeModule;
162
+        audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
163
+
164
+        // Setup runtime device change detection.
165
+        //
166
+
167
+        // Detect changes in wired headset connections.
168
+        IntentFilter wiredHeadSetFilter = new IntentFilter(AudioManager.ACTION_HEADSET_PLUG);
169
+        BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() {
170
+            @Override
171
+            public void onReceive(Context context, Intent intent) {
172
+                JitsiMeetLogger.d(TAG + " Wired headset added / removed");
173
+                onHeadsetDeviceChange();
174
+            }
175
+        };
176
+        context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
177
+
178
+        // Detect Bluetooth device changes.
179
+        bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(context, this);
180
+
181
+        // On Android < M, detect if we have an earpiece.
182
+        PackageManager pm = context.getPackageManager();
183
+        if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
184
+            module.addDevice(AudioModeModule.DEVICE_EARPIECE);
185
+        }
186
+
187
+        // Always assume there is a speaker.
188
+        module.addDevice(AudioModeModule.DEVICE_SPEAKER);
189
+    }
190
+
191
+    @Override
192
+    public void stop() {
193
+        bluetoothHeadsetMonitor.stop();
194
+        bluetoothHeadsetMonitor = null;
195
+    }
196
+
197
+    @Override
198
+    public void setAudioRoute(String device) {
199
+        // Turn bluetooth on / off
200
+        setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH));
201
+
202
+        // Turn speaker on / off
203
+        audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER));
204
+    }
205
+
206
+    @Override
207
+    public boolean setMode(int mode) {
208
+        if (mode == AudioModeModule.DEFAULT) {
209
+            audioFocusLost = false;
210
+            audioManager.setMode(AudioManager.MODE_NORMAL);
211
+            audioManager.abandonAudioFocus(this);
212
+            audioManager.setSpeakerphoneOn(false);
213
+            setBluetoothAudioRoute(false);
214
+
215
+            return true;
216
+        }
217
+
218
+        audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
219
+        audioManager.setMicrophoneMute(false);
220
+
221
+        if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN)
222
+                == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
223
+            JitsiMeetLogger.w(TAG + " Audio focus request failed");
224
+            return false;
225
+        }
226
+
227
+        return true;
228
+    }
229
+}

+ 130
- 428
android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java Voir le fichier

1
 /*
1
 /*
2
- * Copyright @ 2017-present Atlassian Pty Ltd
2
+ * Copyright @ 2017-present 8x8, Inc.
3
  *
3
  *
4
  * Licensed under the Apache License, Version 2.0 (the "License");
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
  * you may not use this file except in compliance with the License.
5
  * you may not use this file except in compliance with the License.
16
 
16
 
17
 package org.jitsi.meet.sdk;
17
 package org.jitsi.meet.sdk;
18
 
18
 
19
-import android.annotation.TargetApi;
20
-import android.content.BroadcastReceiver;
21
 import android.content.Context;
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
 import android.os.Build;
20
 import android.os.Build;
28
-import androidx.annotation.RequiresApi;
29
 
21
 
30
 import com.facebook.react.bridge.Arguments;
22
 import com.facebook.react.bridge.Arguments;
31
 import com.facebook.react.bridge.Promise;
23
 import com.facebook.react.bridge.Promise;
61
  * {@code AudioModeModule.DEFAULT} mode should be used.
53
  * {@code AudioModeModule.DEFAULT} mode should be used.
62
  */
54
  */
63
 @ReactModule(name = AudioModeModule.NAME)
55
 @ReactModule(name = AudioModeModule.NAME)
64
-class AudioModeModule extends ReactContextBaseJavaModule
65
-    implements AudioManager.OnAudioFocusChangeListener {
66
-
56
+class AudioModeModule extends ReactContextBaseJavaModule {
67
     public static final String NAME = "AudioMode";
57
     public static final String NAME = "AudioMode";
68
 
58
 
69
     /**
59
     /**
75
      * - VIDEO_CALL: Used for video calls. It will use the speaker by default,
65
      * - VIDEO_CALL: Used for video calls. It will use the speaker by default,
76
      *   unless a wired or Bluetooth headset is connected.
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
      * The {@code Log} tag {@code AudioModeModule} is to log messages with.
73
      * The {@code Log} tag {@code AudioModeModule} is to log messages with.
100
     static final String TAG = NAME;
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
     static boolean useConnectionService() {
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
      * {@link ExecutorService} for running all audio operations on a dedicated
91
      * {@link ExecutorService} for running all audio operations on a dedicated
185
      * thread.
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
      * Audio mode currently in use.
97
      * Audio mode currently in use.
239
     /**
101
     /**
240
      * Audio device types.
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
      * Device change event.
110
      * Device change event.
259
      */
121
      */
260
     private String selectedDevice;
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
      * User selected device. When null the default is used depending on the
125
      * User selected device. When null the default is used depending on the
273
      * mode.
126
      * mode.
284
     public AudioModeModule(ReactApplicationContext reactContext) {
137
     public AudioModeModule(ReactApplicationContext reactContext) {
285
         super(reactContext);
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
         return NAME;
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
      * Helper function to run operations on a dedicated thread.
213
      * Helper function to run operations on a dedicated thread.
490
      * @param runnable
214
      * @param runnable
491
      */
215
      */
492
-    public void runInAudioThread(Runnable runnable) {
216
+    void runInAudioThread(Runnable runnable) {
493
         executor.execute(runnable);
217
         executor.execute(runnable);
494
     }
218
     }
495
 
219
 
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
      * Public method to set the current audio mode.
246
      * Public method to set the current audio mode.
563
      *
247
      *
587
                     AudioModeModule.this.mode = mode;
271
                     AudioModeModule.this.mode = mode;
588
                     promise.resolve(null);
272
                     promise.resolve(null);
589
                 } else {
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
     private boolean updateAudioRoute(int mode) {
299
     private boolean updateAudioRoute(int mode) {
664
         JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
300
         JitsiMeetLogger.i(TAG + " Update audio route for mode: " + mode);
665
 
301
 
302
+        if (!audioDeviceHandler.setMode(mode)) {
303
+            return false;
304
+        }
305
+
666
         if (mode == DEFAULT) {
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
             selectedDevice = null;
307
             selectedDevice = null;
675
             userSelectedDevice = null;
308
             userSelectedDevice = null;
676
 
309
 
678
             return true;
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
         boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
314
         boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
696
         boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
315
         boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
697
 
316
 
706
         }
325
         }
707
 
326
 
708
         // Consider the user's selection
327
         // Consider the user's selection
709
-        if (userSelectedDevice != null
710
-                && availableDevices.contains(userSelectedDevice)) {
328
+        if (userSelectedDevice != null && availableDevices.contains(userSelectedDevice)) {
711
             audioDevice = userSelectedDevice;
329
             audioDevice = userSelectedDevice;
712
         }
330
         }
713
 
331
 
720
         selectedDevice = audioDevice;
338
         selectedDevice = audioDevice;
721
         JitsiMeetLogger.i(TAG + " Selected audio device: " + audioDevice);
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
         notifyDevicesChanged();
343
         notifyDevicesChanged();
730
         return true;
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
 }

+ 63
- 66
android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java Voir le fichier

1
 /*
1
 /*
2
- * Copyright @ 2017-present Atlassian Pty Ltd
2
+ * Copyright @ 2017-present 8x8, Inc.
3
  *
3
  *
4
  * Licensed under the Apache License, Version 2.0 (the "License");
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
  * you may not use this file except in compliance with the License.
5
  * you may not use this file except in compliance with the License.
33
  * about device changes when this occurs.
33
  * about device changes when this occurs.
34
  */
34
  */
35
 class BluetoothHeadsetMonitor {
35
 class BluetoothHeadsetMonitor {
36
+    private final static String TAG = BluetoothHeadsetMonitor.class.getSimpleName();
37
+
36
     /**
38
     /**
37
-     * {@link AudioModeModule} where this monitor reports.
39
+     * The {@link Context} in which this module executes.
38
      */
40
      */
39
-    private final AudioModeModule audioModeModule;
41
+    private final Context context;
40
 
42
 
41
     /**
43
     /**
42
-     * The {@link Context} in which {@link #audioModeModule} executes.
44
+     * Reference to the {@link BluetoothAdapter} object, used to access Bluetooth functionality.
43
      */
45
      */
44
-    private final Context context;
46
+    private BluetoothAdapter adapter;
45
 
47
 
46
     /**
48
     /**
47
      * Reference to a proxy object which allows us to query connected devices.
49
      * Reference to a proxy object which allows us to query connected devices.
49
     private BluetoothHeadset headset;
51
     private BluetoothHeadset headset;
50
 
52
 
51
     /**
53
     /**
52
-     * Flag indicating if there are any Bluetooth headset devices currently
53
-     * available.
54
+     * receiver registered for receiving Bluetooth connection state changes.
54
      */
55
      */
55
-    private boolean headsetAvailable = false;
56
+    private BroadcastReceiver receiver;
56
 
57
 
57
     /**
58
     /**
58
-     * Helper for running Bluetooth operations on the main thread.
59
+     * Listener for receiving Bluetooth device change events.
59
      */
60
      */
60
-    private final Runnable updateDevicesRunnable
61
-        = new Runnable() {
62
-            @Override
63
-            public void run() {
64
-                headsetAvailable
65
-                    = (headset != null)
66
-                        && !headset.getConnectedDevices().isEmpty();
67
-                audioModeModule.onBluetoothDeviceChange();
68
-            }
69
-        };
61
+    private Listener listener;
70
 
62
 
71
-    public BluetoothHeadsetMonitor(
72
-            AudioModeModule audioModeModule,
73
-            Context context) {
74
-        this.audioModeModule = audioModeModule;
63
+    public BluetoothHeadsetMonitor(Context context, Listener listener) {
75
         this.context = context;
64
         this.context = context;
76
-
77
-        AudioManager audioManager
78
-            = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
79
-
80
-        if (!audioManager.isBluetoothScoAvailableOffCall()) {
81
-            JitsiMeetLogger.w(AudioModeModule.TAG + " Bluetooth SCO is not available");
82
-            return;
83
-        }
84
-
85
-        if (getBluetoothHeadsetProfileProxy()) {
86
-            registerBluetoothReceiver();
87
-
88
-            // Initial detection.
89
-            updateDevices();
90
-        }
65
+        this.listener = listener;
91
     }
66
     }
92
 
67
 
93
     private boolean getBluetoothHeadsetProfileProxy() {
68
     private boolean getBluetoothHeadsetProfileProxy() {
94
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
69
+        adapter = BluetoothAdapter.getDefaultAdapter();
95
 
70
 
96
         if (adapter == null) {
71
         if (adapter == null) {
97
-            JitsiMeetLogger.w(AudioModeModule.TAG + " Device doesn't support Bluetooth");
72
+            JitsiMeetLogger.w(TAG + " Device doesn't support Bluetooth");
98
             return false;
73
             return false;
99
         }
74
         }
100
 
75
 
104
         BluetoothProfile.ServiceListener listener
79
         BluetoothProfile.ServiceListener listener
105
             = new BluetoothProfile.ServiceListener() {
80
             = new BluetoothProfile.ServiceListener() {
106
                 @Override
81
                 @Override
107
-                public void onServiceConnected(
108
-                        int profile,
109
-                        BluetoothProfile proxy) {
82
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
110
                     if (profile == BluetoothProfile.HEADSET) {
83
                     if (profile == BluetoothProfile.HEADSET) {
111
                         headset = (BluetoothHeadset) proxy;
84
                         headset = (BluetoothHeadset) proxy;
112
                         updateDevices();
85
                         updateDevices();
120
                 }
93
                 }
121
             };
94
             };
122
 
95
 
123
-        return
124
-            adapter.getProfileProxy(
125
-                    context,
126
-                    listener,
127
-                    BluetoothProfile.HEADSET);
128
-    }
129
-
130
-    /**
131
-     * Returns the current headset availability.
132
-     *
133
-     * @return {@code true} if there is a Bluetooth headset connected;
134
-     * {@code false}, otherwise.
135
-     */
136
-    public boolean isHeadsetAvailable() {
137
-        return headsetAvailable;
96
+        return adapter.getProfileProxy(context, listener, BluetoothProfile.HEADSET);
138
     }
97
     }
139
 
98
 
140
     private void onBluetoothReceiverReceive(Context context, Intent intent) {
99
     private void onBluetoothReceiverReceive(Context context, Intent intent) {
149
             switch (state) {
108
             switch (state) {
150
             case BluetoothHeadset.STATE_CONNECTED:
109
             case BluetoothHeadset.STATE_CONNECTED:
151
             case BluetoothHeadset.STATE_DISCONNECTED:
110
             case BluetoothHeadset.STATE_DISCONNECTED:
152
-                JitsiMeetLogger.d(AudioModeModule.TAG + " BT headset connection state changed: " + state);
111
+                JitsiMeetLogger.d(TAG + " BT headset connection state changed: " + state);
153
                 updateDevices();
112
                 updateDevices();
154
                 break;
113
                 break;
155
             }
114
             }
157
             // XXX: This action will be fired when the connection established
116
             // XXX: This action will be fired when the connection established
158
             // with a Bluetooth headset (called a SCO connection) changes state.
117
             // with a Bluetooth headset (called a SCO connection) changes state.
159
             // When the SCO connection is active we route audio to it.
118
             // When the SCO connection is active we route audio to it.
160
-            int state
161
-                = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
119
+            int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99);
162
 
120
 
163
             switch (state) {
121
             switch (state) {
164
             case AudioManager.SCO_AUDIO_STATE_CONNECTED:
122
             case AudioManager.SCO_AUDIO_STATE_CONNECTED:
165
             case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
123
             case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
166
-                JitsiMeetLogger.d(AudioModeModule.TAG + " BT SCO connection state changed: " + state);
124
+                JitsiMeetLogger.d(TAG + " BT SCO connection state changed: " + state);
167
                 updateDevices();
125
                 updateDevices();
168
                 break;
126
                 break;
169
             }
127
             }
171
     }
129
     }
172
 
130
 
173
     private void registerBluetoothReceiver() {
131
     private void registerBluetoothReceiver() {
174
-        BroadcastReceiver receiver = new BroadcastReceiver() {
132
+        receiver = new BroadcastReceiver() {
175
             @Override
133
             @Override
176
             public void onReceive(Context context, Intent intent) {
134
             public void onReceive(Context context, Intent intent) {
177
                 onBluetoothReceiverReceive(context, intent);
135
                 onBluetoothReceiverReceive(context, intent);
178
             }
136
             }
179
         };
137
         };
180
-        IntentFilter filter = new IntentFilter();
181
 
138
 
139
+        IntentFilter filter = new IntentFilter();
182
         filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
140
         filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
183
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
141
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
142
+
184
         context.registerReceiver(receiver, filter);
143
         context.registerReceiver(receiver, filter);
185
     }
144
     }
186
 
145
 
187
     /**
146
     /**
188
      * Detects if there are new devices connected / disconnected and fires the
147
      * Detects if there are new devices connected / disconnected and fires the
189
-     * {@link AudioModeModule#onAudioDeviceChange()} callback.
148
+     * {@link Listener} registered event.
190
      */
149
      */
191
     private void updateDevices() {
150
     private void updateDevices() {
192
-        audioModeModule.runInAudioThread(updateDevicesRunnable);
151
+        boolean headsetAvailable = (headset != null) && !headset.getConnectedDevices().isEmpty();
152
+        listener.onBluetoothDeviceChange(headsetAvailable);
153
+    }
154
+
155
+    public void start() {
156
+        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
157
+
158
+        if (!audioManager.isBluetoothScoAvailableOffCall()) {
159
+            JitsiMeetLogger.w(TAG + " Bluetooth SCO is not available");
160
+            return;
161
+        }
162
+
163
+        if (!getBluetoothHeadsetProfileProxy()) {
164
+            JitsiMeetLogger.w(TAG + " Couldn't get BT profile proxy");
165
+            return;
166
+        }
167
+
168
+        registerBluetoothReceiver();
169
+
170
+        // Initial detection.
171
+        updateDevices();
172
+    }
173
+
174
+    public void stop() {
175
+        if (receiver != null) {
176
+            context.unregisterReceiver(receiver);
177
+        }
178
+
179
+        if (adapter != null && headset != null) {
180
+            adapter.closeProfileProxy(BluetoothProfile.HEADSET, headset);
181
+        }
182
+
183
+        receiver = null;
184
+        headset = null;
185
+        adapter = null;
186
+    }
187
+
188
+    interface Listener {
189
+        void onBluetoothDeviceChange(boolean deviceAvailable);
193
     }
190
     }
194
 }
191
 }

+ 3
- 5
android/sdk/src/main/java/org/jitsi/meet/sdk/ConnectionService.java Voir le fichier

403
         @Override
403
         @Override
404
         public void onCallAudioStateChanged(CallAudioState state) {
404
         public void onCallAudioStateChanged(CallAudioState state) {
405
             JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
405
             JitsiMeetLogger.d(TAG + " onCallAudioStateChanged: " + state);
406
-            AudioModeModule audioModeModule
407
-                    = ReactInstanceManagerHolder
408
-                    .getNativeModule(AudioModeModule.class);
409
-            if (audioModeModule != null) {
410
-                audioModeModule.onCallAudioStateChange(state);
406
+            RNConnectionService module = ReactInstanceManagerHolder.getNativeModule(RNConnectionService.class);
407
+            if (module != null) {
408
+                module.onCallAudioStateChange(state);
411
             }
409
             }
412
         }
410
         }
413
 
411
 

+ 31
- 2
android/sdk/src/main/java/org/jitsi/meet/sdk/RNConnectionService.java Voir le fichier

29
  */
29
  */
30
 @RequiresApi(api = Build.VERSION_CODES.O)
30
 @RequiresApi(api = Build.VERSION_CODES.O)
31
 @ReactModule(name = RNConnectionService.NAME)
31
 @ReactModule(name = RNConnectionService.NAME)
32
-class RNConnectionService
33
-    extends ReactContextBaseJavaModule {
32
+class RNConnectionService extends ReactContextBaseJavaModule {
34
 
33
 
35
     public static final String NAME = "ConnectionService";
34
     public static final String NAME = "ConnectionService";
36
 
35
 
37
     private static final String TAG = ConnectionService.TAG;
36
     private static final String TAG = ConnectionService.TAG;
38
 
37
 
38
+    /**
39
+     * Handler for dealing with call state changes. We are acting as a proxy between ConnectionService
40
+     * and other modules such as {@link AudioModeModule}.
41
+     */
42
+    private CallAudioStateListener callAudioStateListener;
43
+
39
     /**
44
     /**
40
      * Sets the audio route on all existing {@link android.telecom.Connection}s
45
      * Sets the audio route on all existing {@link android.telecom.Connection}s
41
      *
46
      *
168
     public void updateCall(String callUUID, ReadableMap callState) {
173
     public void updateCall(String callUUID, ReadableMap callState) {
169
         ConnectionService.updateCall(callUUID, callState);
174
         ConnectionService.updateCall(callUUID, callState);
170
     }
175
     }
176
+
177
+    public CallAudioStateListener getCallAudioStateListener() {
178
+        return callAudioStateListener;
179
+    }
180
+
181
+    public void setCallAudioStateListener(CallAudioStateListener callAudioStateListener) {
182
+        this.callAudioStateListener = callAudioStateListener;
183
+    }
184
+
185
+    /**
186
+     * Handler for call state changes. {@code ConnectionServiceImpl} will call this handler when the
187
+     * call audio state changes.
188
+     *
189
+     * @param callAudioState The current call's audio state.
190
+     */
191
+    void onCallAudioStateChange(android.telecom.CallAudioState callAudioState) {
192
+        if (callAudioStateListener != null) {
193
+            callAudioStateListener.onCallAudioStateChange(callAudioState);
194
+        }
195
+    }
196
+
197
+    interface CallAudioStateListener {
198
+        void onCallAudioStateChange(android.telecom.CallAudioState callAudioState);
199
+    }
171
 }
200
 }

Chargement…
Annuler
Enregistrer