react-native-altibbi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +190 -0
- package/android/build.gradle +104 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/altibbi/AltibbiModule.kt +18 -0
- package/android/src/main/java/com/altibbi/AltibbiPackage.kt +18 -0
- package/android/src/main/java/com/altibbi/OTCustomAudioDevice.java +1146 -0
- package/android/src/main/java/com/altibbi/OTPublisherLayout.java +61 -0
- package/android/src/main/java/com/altibbi/OTPublisherViewManager.java +30 -0
- package/android/src/main/java/com/altibbi/OTRN.java +101 -0
- package/android/src/main/java/com/altibbi/OTScreenCapturer.java +120 -0
- package/android/src/main/java/com/altibbi/OTSessionManager.java +1281 -0
- package/android/src/main/java/com/altibbi/OTSubscriberLayout.java +68 -0
- package/android/src/main/java/com/altibbi/OTSubscriberViewManager.java +30 -0
- package/android/src/main/java/com/altibbi/Socket.kt +294 -0
- package/android/src/main/java/com/altibbi/SocketEventEmitter.kt +25 -0
- package/android/src/main/java/com/altibbi/utils/EventUtils.java +189 -0
- package/android/src/main/java/com/altibbi/utils/Utils.java +135 -0
- package/ios/Altibbi-Bridging-Header.h +6 -0
- package/ios/Altibbi.mm +10 -0
- package/ios/Altibbi.swift +4 -0
- package/ios/OTCustomAudioDriver.swift +696 -0
- package/ios/OTPublisher.m +16 -0
- package/ios/OTPublisherManager.swift +21 -0
- package/ios/OTPublisherView.swift +28 -0
- package/ios/OTRN.swift +27 -0
- package/ios/OTScreenCapture.h +27 -0
- package/ios/OTScreenCapture.m +171 -0
- package/ios/OTSessionManager.m +127 -0
- package/ios/OTSessionManager.swift +866 -0
- package/ios/OTSubscriber.m +15 -0
- package/ios/OTSubscriberManager.swift +21 -0
- package/ios/OTSubscriberView.swift +29 -0
- package/ios/OpenTokReactNative.h +13 -0
- package/ios/OpenTokReactNative.m +13 -0
- package/ios/SocketReactNative.m +38 -0
- package/ios/SocketReactNative.swift +276 -0
- package/ios/Utils/EventUtils.swift +143 -0
- package/ios/Utils/Utils.swift +126 -0
- package/lib/commonjs/connection.js +200 -0
- package/lib/commonjs/connection.js.map +1 -0
- package/lib/commonjs/data.js +12 -0
- package/lib/commonjs/data.js.map +1 -0
- package/lib/commonjs/index.js +275 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/scoket.js +245 -0
- package/lib/commonjs/scoket.js.map +1 -0
- package/lib/commonjs/service.js +21 -0
- package/lib/commonjs/service.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/video/OT.js +57 -0
- package/lib/commonjs/video/OT.js.map +1 -0
- package/lib/commonjs/video/OTError.js +17 -0
- package/lib/commonjs/video/OTError.js.map +1 -0
- package/lib/commonjs/video/OTPublisher.js +171 -0
- package/lib/commonjs/video/OTPublisher.js.map +1 -0
- package/lib/commonjs/video/OTSession.js +205 -0
- package/lib/commonjs/video/OTSession.js.map +1 -0
- package/lib/commonjs/video/OTSubscriber.js +185 -0
- package/lib/commonjs/video/OTSubscriber.js.map +1 -0
- package/lib/commonjs/video/contexts/OTContext.js +11 -0
- package/lib/commonjs/video/contexts/OTContext.js.map +1 -0
- package/lib/commonjs/video/helpers/OTHelper.js +92 -0
- package/lib/commonjs/video/helpers/OTHelper.js.map +1 -0
- package/lib/commonjs/video/helpers/OTPublisherHelper.js +117 -0
- package/lib/commonjs/video/helpers/OTPublisherHelper.js.map +1 -0
- package/lib/commonjs/video/helpers/OTSessionHelper.js +206 -0
- package/lib/commonjs/video/helpers/OTSessionHelper.js.map +1 -0
- package/lib/commonjs/video/helpers/OTSubscriberHelper.js +121 -0
- package/lib/commonjs/video/helpers/OTSubscriberHelper.js.map +1 -0
- package/lib/commonjs/video/index.js +42 -0
- package/lib/commonjs/video/index.js.map +1 -0
- package/lib/commonjs/video/views/OTPublisherView.js +26 -0
- package/lib/commonjs/video/views/OTPublisherView.js.map +1 -0
- package/lib/commonjs/video/views/OTSubscriberView.js +25 -0
- package/lib/commonjs/video/views/OTSubscriberView.js.map +1 -0
- package/lib/module/connection.js +180 -0
- package/lib/module/connection.js.map +1 -0
- package/lib/module/data.js +6 -0
- package/lib/module/data.js.map +1 -0
- package/lib/module/index.js +12 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/scoket.js +235 -0
- package/lib/module/scoket.js.map +1 -0
- package/lib/module/service.js +14 -0
- package/lib/module/service.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/video/OT.js +49 -0
- package/lib/module/video/OT.js.map +1 -0
- package/lib/module/video/OTError.js +10 -0
- package/lib/module/video/OTError.js.map +1 -0
- package/lib/module/video/OTPublisher.js +162 -0
- package/lib/module/video/OTPublisher.js.map +1 -0
- package/lib/module/video/OTSession.js +195 -0
- package/lib/module/video/OTSession.js.map +1 -0
- package/lib/module/video/OTSubscriber.js +175 -0
- package/lib/module/video/OTSubscriber.js.map +1 -0
- package/lib/module/video/contexts/OTContext.js +4 -0
- package/lib/module/video/contexts/OTContext.js.map +1 -0
- package/lib/module/video/helpers/OTHelper.js +82 -0
- package/lib/module/video/helpers/OTHelper.js.map +1 -0
- package/lib/module/video/helpers/OTPublisherHelper.js +110 -0
- package/lib/module/video/helpers/OTPublisherHelper.js.map +1 -0
- package/lib/module/video/helpers/OTSessionHelper.js +195 -0
- package/lib/module/video/helpers/OTSessionHelper.js.map +1 -0
- package/lib/module/video/helpers/OTSubscriberHelper.js +112 -0
- package/lib/module/video/helpers/OTSubscriberHelper.js.map +1 -0
- package/lib/module/video/index.js +7 -0
- package/lib/module/video/index.js.map +1 -0
- package/lib/module/video/views/OTPublisherView.js +18 -0
- package/lib/module/video/views/OTPublisherView.js.map +1 -0
- package/lib/module/video/views/OTSubscriberView.js +17 -0
- package/lib/module/video/views/OTSubscriberView.js.map +1 -0
- package/lib/typescript/src/connection.d.ts +40 -0
- package/lib/typescript/src/connection.d.ts.map +1 -0
- package/lib/typescript/src/data.d.ts +7 -0
- package/lib/typescript/src/data.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +12 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/scoket.d.ts +100 -0
- package/lib/typescript/src/scoket.d.ts.map +1 -0
- package/lib/typescript/src/service.d.ts +9 -0
- package/lib/typescript/src/service.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +22 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +178 -0
- package/react-native-altibbi.podspec +46 -0
- package/src/connection.ts +255 -0
- package/src/data.ts +21 -0
- package/src/index.tsx +80 -0
- package/src/scoket.ts +365 -0
- package/src/service.ts +20 -0
- package/src/types.ts +22 -0
- package/src/video/OT.js +65 -0
- package/src/video/OTError.js +14 -0
- package/src/video/OTPublisher.js +193 -0
- package/src/video/OTSession.js +168 -0
- package/src/video/OTSubscriber.js +148 -0
- package/src/video/contexts/OTContext.js +5 -0
- package/src/video/helpers/OTHelper.js +91 -0
- package/src/video/helpers/OTPublisherHelper.js +122 -0
- package/src/video/helpers/OTSessionHelper.js +233 -0
- package/src/video/helpers/OTSubscriberHelper.js +125 -0
- package/src/video/index.js +13 -0
- package/src/video/views/OTPublisherView.js +19 -0
- package/src/video/views/OTSubscriberView.js +18 -0
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
package com.altibbi;
|
|
2
|
+
|
|
3
|
+
import android.bluetooth.BluetoothAdapter;
|
|
4
|
+
import android.bluetooth.BluetoothDevice;
|
|
5
|
+
import android.bluetooth.BluetoothHeadset;
|
|
6
|
+
import android.bluetooth.BluetoothProfile;
|
|
7
|
+
import android.content.BroadcastReceiver;
|
|
8
|
+
import android.content.Context;
|
|
9
|
+
import android.content.Intent;
|
|
10
|
+
import android.content.IntentFilter;
|
|
11
|
+
import android.media.AudioFormat;
|
|
12
|
+
import android.media.AudioManager;
|
|
13
|
+
import android.media.AudioRecord;
|
|
14
|
+
import android.media.AudioTrack;
|
|
15
|
+
import android.media.MediaRecorder.AudioSource;
|
|
16
|
+
import android.media.audiofx.AcousticEchoCanceler;
|
|
17
|
+
import android.media.audiofx.NoiseSuppressor;
|
|
18
|
+
import android.os.Build;
|
|
19
|
+
import android.os.Handler;
|
|
20
|
+
import android.telephony.PhoneStateListener;
|
|
21
|
+
import android.telephony.TelephonyManager;
|
|
22
|
+
import android.util.Log;
|
|
23
|
+
|
|
24
|
+
import com.opentok.android.BaseAudioDevice;
|
|
25
|
+
|
|
26
|
+
import java.nio.ByteBuffer;
|
|
27
|
+
import java.util.List;
|
|
28
|
+
import java.util.Locale;
|
|
29
|
+
import java.util.concurrent.locks.Condition;
|
|
30
|
+
import java.util.concurrent.locks.ReentrantLock;
|
|
31
|
+
|
|
32
|
+
class OTCustomAudioDriver extends BaseAudioDevice {
|
|
33
|
+
private final static String TAG = OTCustomAudioDriver.class.getSimpleName();
|
|
34
|
+
|
|
35
|
+
private static final int NUM_CHANNELS_CAPTURING = 1;
|
|
36
|
+
private static final int NUM_CHANNELS_RENDERING = 2;
|
|
37
|
+
private static final int STEREO_CHANNELS = 2;
|
|
38
|
+
private static final int DEFAULT_SAMPLE_RATE = 44100;
|
|
39
|
+
private static final int SAMPLE_SIZE_IN_BYTES = 2;
|
|
40
|
+
private static final int DEFAULT_SAMPLES_PER_BUFFER = (DEFAULT_SAMPLE_RATE / 1000) * 10; // 10ms
|
|
41
|
+
private static final int DEFAULT_BUFFER_SIZE =
|
|
42
|
+
SAMPLE_SIZE_IN_BYTES * DEFAULT_SAMPLES_PER_BUFFER * STEREO_CHANNELS;
|
|
43
|
+
// Max 10 ms @ 48 kHz - Stereo
|
|
44
|
+
private static final int DEFAULT_START_RENDERER_AND_CAPTURER_DELAY = 5 * 1000;
|
|
45
|
+
private static final int DEFAULT_BLUETOOTH_SCO_START_DELAY = 2000;
|
|
46
|
+
|
|
47
|
+
private Context context;
|
|
48
|
+
|
|
49
|
+
private AudioTrack audioTrack;
|
|
50
|
+
private AudioRecord audioRecord;
|
|
51
|
+
|
|
52
|
+
// Capture & render buffers
|
|
53
|
+
private ByteBuffer playBuffer;
|
|
54
|
+
private ByteBuffer recBuffer;
|
|
55
|
+
private byte[] tempBufPlay;
|
|
56
|
+
private byte[] tempBufRec;
|
|
57
|
+
|
|
58
|
+
private final ReentrantLock rendererLock = new ReentrantLock(true);
|
|
59
|
+
private final Condition renderEvent = rendererLock.newCondition();
|
|
60
|
+
private volatile boolean isRendering = false;
|
|
61
|
+
private volatile boolean shutdownRenderThread = false;
|
|
62
|
+
|
|
63
|
+
private final ReentrantLock captureLock = new ReentrantLock(true);
|
|
64
|
+
private final Condition captureEvent = captureLock.newCondition();
|
|
65
|
+
private volatile boolean isCapturing = false;
|
|
66
|
+
private volatile boolean shutdownCaptureThread = false;
|
|
67
|
+
|
|
68
|
+
private AudioSettings captureSettings;
|
|
69
|
+
private AudioSettings rendererSettings;
|
|
70
|
+
private NoiseSuppressor noiseSuppressor;
|
|
71
|
+
private AcousticEchoCanceler echoCanceler;
|
|
72
|
+
|
|
73
|
+
// Capturing delay estimation
|
|
74
|
+
private int estimatedCaptureDelay = 0;
|
|
75
|
+
|
|
76
|
+
// Rendering delay estimation
|
|
77
|
+
private int bufferedPlaySamples = 0;
|
|
78
|
+
private int playPosition = 0;
|
|
79
|
+
private int estimatedRenderDelay = 0;
|
|
80
|
+
|
|
81
|
+
private AudioManager audioManager;
|
|
82
|
+
private AudioManagerMode audioManagerMode = new AudioManagerMode();
|
|
83
|
+
|
|
84
|
+
private int outputSamplingRate = DEFAULT_SAMPLE_RATE;
|
|
85
|
+
private int captureSamplingRate = DEFAULT_SAMPLE_RATE;
|
|
86
|
+
private int samplesPerBuffer = DEFAULT_SAMPLES_PER_BUFFER;
|
|
87
|
+
|
|
88
|
+
// For headset receiver.
|
|
89
|
+
private static final String HEADSET_PLUG_STATE_KEY = "state";
|
|
90
|
+
|
|
91
|
+
private BluetoothState bluetoothState;
|
|
92
|
+
private BluetoothAdapter bluetoothAdapter;
|
|
93
|
+
private BluetoothProfile bluetoothProfile;
|
|
94
|
+
private final Object bluetoothLock = new Object();
|
|
95
|
+
private TelephonyManager telephonyManager;
|
|
96
|
+
|
|
97
|
+
private boolean isPaused;
|
|
98
|
+
|
|
99
|
+
private enum OutputType {
|
|
100
|
+
SPEAKER_PHONE,
|
|
101
|
+
EAR_PIECE,
|
|
102
|
+
HEAD_PHONES,
|
|
103
|
+
BLUETOOTH
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private OutputType audioOutputType = OutputType.SPEAKER_PHONE;
|
|
107
|
+
|
|
108
|
+
private OutputType getOutputType() {
|
|
109
|
+
return audioOutputType;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private void setOutputType(OutputType type) {
|
|
113
|
+
audioOutputType = type;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private static class AudioManagerMode {
|
|
117
|
+
private int oldMode;
|
|
118
|
+
private int naquire;
|
|
119
|
+
|
|
120
|
+
AudioManagerMode() {
|
|
121
|
+
oldMode = 0;
|
|
122
|
+
naquire = 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void acquireMode(AudioManager audioManager) {
|
|
126
|
+
if (0 == naquire++) {
|
|
127
|
+
oldMode = audioManager.getMode();
|
|
128
|
+
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void releaseMode(AudioManager audioManager) {
|
|
133
|
+
if (0 == --naquire) {
|
|
134
|
+
audioManager.setMode(oldMode);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private static class AudioState {
|
|
140
|
+
private int lastStreamVolume = 0;
|
|
141
|
+
private int lastKnownFocusState = 0;
|
|
142
|
+
private OutputType lastOutputType = OutputType.SPEAKER_PHONE;
|
|
143
|
+
|
|
144
|
+
int getLastStreamVolume() {
|
|
145
|
+
return lastStreamVolume;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
void setLastStreamVolume(int lastStreamVolume) {
|
|
149
|
+
this.lastStreamVolume = lastStreamVolume;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
int getLastKnownFocusState() {
|
|
153
|
+
return lastKnownFocusState;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
void setLastKnownFocusState(int lastKnownFocusState) {
|
|
157
|
+
this.lastKnownFocusState = lastKnownFocusState;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
OutputType getLastOutputType() {
|
|
161
|
+
return this.lastOutputType;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
void setLastOutputType(OutputType lastOutputType) {
|
|
165
|
+
this.lastOutputType = lastOutputType;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private AudioState audioState = new AudioState();
|
|
171
|
+
|
|
172
|
+
private BroadcastReceiver headsetBroadcastReceiver = new BroadcastReceiver() {
|
|
173
|
+
@Override
|
|
174
|
+
public void onReceive(Context context, Intent intent) {
|
|
175
|
+
Log.d(TAG, "headsetBroadcastReceiver.onReceive()");
|
|
176
|
+
if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
|
|
177
|
+
if (intent.getIntExtra(HEADSET_PLUG_STATE_KEY, 0) == 1) {
|
|
178
|
+
Log.d(TAG, "headsetBroadcastReceiver.onReceive(): Headphones connected");
|
|
179
|
+
audioState.setLastOutputType(getOutputType());
|
|
180
|
+
setOutputType(OutputType.HEAD_PHONES);
|
|
181
|
+
audioManager.setSpeakerphoneOn(false);
|
|
182
|
+
audioManager.setBluetoothScoOn(false);
|
|
183
|
+
} else {
|
|
184
|
+
Log.d(TAG, "headsetBroadcastReceiver.onReceive(): Headphones disconnected");
|
|
185
|
+
if (getOutputType() == OutputType.HEAD_PHONES) {
|
|
186
|
+
if (audioState.getLastOutputType() == OutputType.BLUETOOTH &&
|
|
187
|
+
BluetoothState.Connected == bluetoothState) {
|
|
188
|
+
audioManager.setBluetoothScoOn(true);
|
|
189
|
+
startBluetoothSco();
|
|
190
|
+
setOutputType(OutputType.BLUETOOTH);
|
|
191
|
+
} else {
|
|
192
|
+
if (audioState.getLastOutputType() == OutputType.SPEAKER_PHONE) {
|
|
193
|
+
setOutputType(OutputType.SPEAKER_PHONE);
|
|
194
|
+
audioManager.setSpeakerphoneOn(true);
|
|
195
|
+
}
|
|
196
|
+
if (audioState.getLastOutputType() == OutputType.EAR_PIECE) {
|
|
197
|
+
setOutputType(OutputType.EAR_PIECE);
|
|
198
|
+
audioManager.setSpeakerphoneOn(false);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Intent broadcast receiver which handles changes in Bluetooth device availability.
|
|
208
|
+
// Detects headset changes and Bluetooth SCO state changes.
|
|
209
|
+
private final BroadcastReceiver bluetoothHeadsetReceiver = new BroadcastReceiver() {
|
|
210
|
+
@Override
|
|
211
|
+
public void onReceive(Context context, Intent intent) {
|
|
212
|
+
final String action = intent.getAction();
|
|
213
|
+
if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
|
|
214
|
+
final int state = intent.getIntExtra(
|
|
215
|
+
BluetoothHeadset.EXTRA_STATE, -1);
|
|
216
|
+
switch (state) {
|
|
217
|
+
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
|
|
218
|
+
Log.d(TAG, "bluetoothHeadsetReceiver.onReceive(): STATE_AUDIO_DISCONNECTED");
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case BluetoothHeadset.STATE_AUDIO_CONNECTING:
|
|
222
|
+
Log.d(TAG, "bluetoothHeadsetReceiver.onReceive(): STATE_AUDIO_CONNECTING");
|
|
223
|
+
break;
|
|
224
|
+
|
|
225
|
+
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
|
|
226
|
+
Log.d(TAG, "bluetoothHeadsetReceiver.onReceive(): STATE_AUDIO_CONNECTED");
|
|
227
|
+
break;
|
|
228
|
+
|
|
229
|
+
default:
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
private void restoreAudioAfterBluetoothDisconnect() {
|
|
237
|
+
if (audioManager.isWiredHeadsetOn()) {
|
|
238
|
+
setOutputType(OutputType.HEAD_PHONES);
|
|
239
|
+
audioManager.setSpeakerphoneOn(false);
|
|
240
|
+
} else {
|
|
241
|
+
if (audioState.getLastOutputType() == OutputType.SPEAKER_PHONE) {
|
|
242
|
+
setOutputType(OutputType.SPEAKER_PHONE);
|
|
243
|
+
super.setOutputMode(OutputMode.SpeakerPhone);
|
|
244
|
+
audioManager.setSpeakerphoneOn(true);
|
|
245
|
+
} else if (audioState.getLastOutputType() == OutputType.EAR_PIECE) {
|
|
246
|
+
setOutputType(OutputType.EAR_PIECE);
|
|
247
|
+
super.setOutputMode(OutputMode.Handset);
|
|
248
|
+
audioManager.setSpeakerphoneOn(false);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private final BroadcastReceiver bluetoothBroadcastReceiver = new BroadcastReceiver() {
|
|
254
|
+
public void onReceive(Context context, Intent intent) {
|
|
255
|
+
final String action = intent.getAction();
|
|
256
|
+
if (null != action && action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
|
|
257
|
+
int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -1);
|
|
258
|
+
switch (state) {
|
|
259
|
+
case BluetoothHeadset.STATE_CONNECTED:
|
|
260
|
+
Log.d(TAG, "bluetoothBroadcastReceiver.onReceive(): BluetoothHeadset.STATE_CONNECTED");
|
|
261
|
+
new Handler().postDelayed(() -> connectBluetooth(), DEFAULT_BLUETOOTH_SCO_START_DELAY);
|
|
262
|
+
break;
|
|
263
|
+
case BluetoothHeadset.STATE_DISCONNECTING:
|
|
264
|
+
Log.d(TAG, "bluetoothBroadcastReceiver.onReceive(): BluetoothHeadset.STATE_DISCONNECTING");
|
|
265
|
+
break;
|
|
266
|
+
case BluetoothHeadset.STATE_DISCONNECTED:
|
|
267
|
+
Log.d(TAG, "bluetoothBroadcastReceiver.onReceive(): BluetoothHeadset.STATE_DISCONNECTED");
|
|
268
|
+
stopBluetoothSco();
|
|
269
|
+
audioManager.setBluetoothScoOn(false);
|
|
270
|
+
break;
|
|
271
|
+
default:
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
} else if (null != action && action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
|
|
275
|
+
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
|
|
276
|
+
switch (state) {
|
|
277
|
+
case AudioManager.SCO_AUDIO_STATE_CONNECTED:
|
|
278
|
+
Log.d(TAG, "bluetoothBroadcastReceiver.onReceive(): AudioManager.SCO_AUDIO_STATE_CONNECTED");
|
|
279
|
+
bluetoothState = BluetoothState.Connected;
|
|
280
|
+
setOutputType(OutputType.BLUETOOTH);
|
|
281
|
+
OTCustomAudioDriver.super.setOutputMode(OutputMode.Handset); // When BT is connected it replaces the handset
|
|
282
|
+
break;
|
|
283
|
+
case AudioManager.SCO_AUDIO_STATE_ERROR:
|
|
284
|
+
Log.d(TAG, "bluetoothBroadcastReceiver.onReceive(): AudioManager.SCO_AUDIO_STATE_ERROR");
|
|
285
|
+
break;
|
|
286
|
+
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
|
|
287
|
+
Log.d(TAG, "bluetoothBroadcastReceiver.onReceive(): AudioManager.SCO_AUDIO_STATE_DISCONNECTED");
|
|
288
|
+
restoreAudioAfterBluetoothDisconnect();
|
|
289
|
+
bluetoothState = BluetoothState.Disconnected;
|
|
290
|
+
break;
|
|
291
|
+
case AudioManager.SCO_AUDIO_STATE_CONNECTING:
|
|
292
|
+
Log.d(TAG, "bluetoothBroadcastReceiver.onReceive(): AudioManager.SCO_AUDIO_STATE_CONNECTING");
|
|
293
|
+
break;
|
|
294
|
+
default:
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
private PhoneStateListener phoneStateListener = new PhoneStateListener(){
|
|
301
|
+
@Override
|
|
302
|
+
public void onCallStateChanged(int state, String incomingNumber) {
|
|
303
|
+
Log.d(TAG, "PhoneStateListener.onCallStateChanged()");
|
|
304
|
+
|
|
305
|
+
super.onCallStateChanged(state, incomingNumber);
|
|
306
|
+
switch (state) {
|
|
307
|
+
|
|
308
|
+
case TelephonyManager.CALL_STATE_IDLE:
|
|
309
|
+
//Initial state
|
|
310
|
+
Log.d(TAG, "PhoneStateListener.onCallStateChanged(): TelephonyManager.CALL_STATE_IDLE");
|
|
311
|
+
// We delay a bit here the action of start capturing and rendering again because Android has to
|
|
312
|
+
// finish routing audio to the earpiece. It is an Android behaviour we have to deal with.
|
|
313
|
+
new Handler().postDelayed(() -> startRendererAndCapturer(), DEFAULT_START_RENDERER_AND_CAPTURER_DELAY);
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case TelephonyManager.CALL_STATE_RINGING:
|
|
317
|
+
// Incoming call Ringing
|
|
318
|
+
Log.d(TAG, "PhoneStateListener.onCallStateChanged(): TelephonyManager.CALL_STATE_RINGING");
|
|
319
|
+
stopRendererAndCapturer();
|
|
320
|
+
break;
|
|
321
|
+
|
|
322
|
+
case TelephonyManager.CALL_STATE_OFFHOOK:
|
|
323
|
+
// Outgoing Call | Accepted incoming call
|
|
324
|
+
Log.d(TAG, "PhoneStateListener.onCallStateChanged(): TelephonyManager.CALL_STATE_OFFHOOK");
|
|
325
|
+
stopRendererAndCapturer();
|
|
326
|
+
break;
|
|
327
|
+
|
|
328
|
+
default:
|
|
329
|
+
Log.d(TAG, "PhoneStateListener.onCallStateChanged() default");
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
private boolean wasRendering;
|
|
336
|
+
private boolean wasCapturing;
|
|
337
|
+
|
|
338
|
+
private void startRendererAndCapturer() {
|
|
339
|
+
if (wasRendering) {
|
|
340
|
+
startRenderer();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (wasCapturing) {
|
|
344
|
+
startCapturer();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private void stopRendererAndCapturer() {
|
|
349
|
+
if (isRendering) {
|
|
350
|
+
stopRenderer();
|
|
351
|
+
wasRendering = true;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (isCapturing) {
|
|
355
|
+
stopCapturer();
|
|
356
|
+
wasCapturing = true;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
|
|
361
|
+
@Override
|
|
362
|
+
public void onAudioFocusChange(int focusChange) {
|
|
363
|
+
Log.d(TAG, "AudioManager.OnAudioFocusChangeListener.onAudioFocusChange(" + focusChange + ")");
|
|
364
|
+
switch (focusChange) {
|
|
365
|
+
case AudioManager.AUDIOFOCUS_GAIN:
|
|
366
|
+
Log.d(TAG, "AudioManager.OnAudioFocusChangeListener.onAudioFocusChange(" + focusChange + "): ");
|
|
367
|
+
//Check if coming back from a complete loss or a transient loss
|
|
368
|
+
switch (audioState.getLastKnownFocusState()) {
|
|
369
|
+
case AudioManager.AUDIOFOCUS_LOSS:
|
|
370
|
+
break;
|
|
371
|
+
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
372
|
+
break;
|
|
373
|
+
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
|
374
|
+
audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
|
|
375
|
+
audioState.getLastStreamVolume(), 0);
|
|
376
|
+
break;
|
|
377
|
+
default:
|
|
378
|
+
Log.d(TAG, "focusChange = " + focusChange);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
setOutputType(audioState.getLastOutputType());
|
|
382
|
+
connectBluetooth();
|
|
383
|
+
forceInvokeConnectBluetooth();
|
|
384
|
+
break;
|
|
385
|
+
|
|
386
|
+
case AudioManager.AUDIOFOCUS_LOSS:
|
|
387
|
+
// -1 Loss for indefinite time
|
|
388
|
+
Log.d(TAG, "AudioManager.OnAudioFocusChangeListener.onAudioFocusChange(" + focusChange + "): AudioManager.AUDIOFOCUS_LOSS");
|
|
389
|
+
break;
|
|
390
|
+
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
391
|
+
// -2 Loss for short duration
|
|
392
|
+
Log.d(TAG, "AudioManager.OnAudioFocusChangeListener.onAudioFocusChange(" + focusChange + "): AudioManager.AUDIOFOCUS_LOSS_TRANSIENT");
|
|
393
|
+
break;
|
|
394
|
+
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
|
395
|
+
// -3 stay quite in background
|
|
396
|
+
Log.d(TAG, "AudioManager.OnAudioFocusChangeListener.onAudioFocusChange(" + focusChange + "): AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
|
|
397
|
+
audioState.setLastStreamVolume(audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL));
|
|
398
|
+
audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 0, 0);
|
|
399
|
+
break;
|
|
400
|
+
case AudioManager.AUDIOFOCUS_NONE:
|
|
401
|
+
Log.d(TAG, "AudioManager.OnAudioFocusChangeListener.onAudioFocusChange(" + focusChange + "): AudioManager.AUDIOFOCUS_NONE");
|
|
402
|
+
break;
|
|
403
|
+
default:
|
|
404
|
+
Log.d(TAG, "AudioManager.OnAudioFocusChangeListener.onAudioFocusChange(" + focusChange + "): default");
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
audioState.setLastOutputType(getOutputType());
|
|
408
|
+
audioState.setLastKnownFocusState(focusChange);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
private void connectBluetooth() {
|
|
413
|
+
Log.d(TAG, "connectBluetooth() called");
|
|
414
|
+
audioManager.setBluetoothScoOn(true);
|
|
415
|
+
startBluetoothSco();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
public OTCustomAudioDriver(Context context) {
|
|
419
|
+
this.context = context;
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
recBuffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);
|
|
423
|
+
} catch (Exception e) {
|
|
424
|
+
Log.e(TAG, e.getMessage());
|
|
425
|
+
}
|
|
426
|
+
tempBufRec = new byte[DEFAULT_BUFFER_SIZE];
|
|
427
|
+
|
|
428
|
+
audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
|
|
429
|
+
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
430
|
+
bluetoothProfile = null;
|
|
431
|
+
|
|
432
|
+
int outputBufferSize = DEFAULT_BUFFER_SIZE;
|
|
433
|
+
|
|
434
|
+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
|
|
435
|
+
try {
|
|
436
|
+
outputSamplingRate = Integer.parseInt(
|
|
437
|
+
audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
|
|
438
|
+
} finally {
|
|
439
|
+
if (outputSamplingRate == 0) {
|
|
440
|
+
outputSamplingRate = DEFAULT_SAMPLE_RATE;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
samplesPerBuffer = Integer.parseInt(
|
|
445
|
+
audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
|
|
446
|
+
outputBufferSize = SAMPLE_SIZE_IN_BYTES
|
|
447
|
+
* samplesPerBuffer
|
|
448
|
+
* NUM_CHANNELS_RENDERING;
|
|
449
|
+
} catch(NumberFormatException numberFormatException) {
|
|
450
|
+
Log.e(TAG, "DefaultAudioDevice(): " + numberFormatException.getMessage());
|
|
451
|
+
} finally {
|
|
452
|
+
if (outputBufferSize == 0) {
|
|
453
|
+
outputBufferSize = DEFAULT_BUFFER_SIZE;
|
|
454
|
+
samplesPerBuffer = DEFAULT_SAMPLES_PER_BUFFER;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
playBuffer = ByteBuffer.allocateDirect(outputBufferSize);
|
|
461
|
+
} catch (Exception e) {
|
|
462
|
+
Log.e(TAG, e.getMessage());
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
tempBufPlay = new byte[outputBufferSize];
|
|
466
|
+
|
|
467
|
+
captureSettings = new AudioSettings(captureSamplingRate, NUM_CHANNELS_CAPTURING);
|
|
468
|
+
rendererSettings = new AudioSettings(outputSamplingRate, NUM_CHANNELS_RENDERING);
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
|
472
|
+
} catch (SecurityException e) {
|
|
473
|
+
e.printStackTrace();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
isPhoneStateListenerRegistered = false;
|
|
477
|
+
wasCapturing = false;
|
|
478
|
+
wasRendering = false;
|
|
479
|
+
isPaused = false;
|
|
480
|
+
Log.d(TAG, "DefaultAudioDevice() exit " + this);
|
|
481
|
+
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@Override
|
|
485
|
+
public boolean initCapturer() {
|
|
486
|
+
// get the minimum buffer size that can be used
|
|
487
|
+
int minRecBufSize = AudioRecord.getMinBufferSize(
|
|
488
|
+
captureSettings.getSampleRate(),
|
|
489
|
+
NUM_CHANNELS_CAPTURING == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO,
|
|
490
|
+
AudioFormat.ENCODING_PCM_16BIT
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
// double size to be more safe
|
|
494
|
+
int recBufSize = minRecBufSize * 2;
|
|
495
|
+
|
|
496
|
+
// release the object
|
|
497
|
+
if (noiseSuppressor != null) {
|
|
498
|
+
noiseSuppressor.release();
|
|
499
|
+
noiseSuppressor = null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (echoCanceler != null) {
|
|
503
|
+
echoCanceler.release();
|
|
504
|
+
echoCanceler = null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (audioRecord != null) {
|
|
508
|
+
audioRecord.release();
|
|
509
|
+
audioRecord = null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
int channelConfig = NUM_CHANNELS_CAPTURING == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO;
|
|
514
|
+
|
|
515
|
+
audioRecord = new AudioRecord(
|
|
516
|
+
AudioSource.VOICE_COMMUNICATION,
|
|
517
|
+
captureSettings.getSampleRate(),
|
|
518
|
+
channelConfig,
|
|
519
|
+
AudioFormat.ENCODING_PCM_16BIT, recBufSize);
|
|
520
|
+
|
|
521
|
+
if (NoiseSuppressor.isAvailable()) {
|
|
522
|
+
noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId());
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (AcousticEchoCanceler.isAvailable()) {
|
|
526
|
+
echoCanceler = AcousticEchoCanceler.create(audioRecord.getAudioSessionId());
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
} catch (Exception e) {
|
|
530
|
+
throw new RuntimeException(e.getMessage());
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Check that the audioRecord is ready to be used.
|
|
534
|
+
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
|
535
|
+
String errorDescription = String.format(Locale.getDefault(), "Audio capture could not be initialized.\n" +
|
|
536
|
+
"Requested parameters\n" +
|
|
537
|
+
" Sampling Rate: %d\n" +
|
|
538
|
+
" Number of channels: %d\n" +
|
|
539
|
+
" Buffer size: %d\n",
|
|
540
|
+
captureSettings.getSampleRate(),
|
|
541
|
+
captureSettings.getNumChannels(),
|
|
542
|
+
minRecBufSize);
|
|
543
|
+
Log.e(TAG, errorDescription);
|
|
544
|
+
throw new RuntimeException(errorDescription);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
registerPhoneStateListener();
|
|
548
|
+
|
|
549
|
+
shutdownCaptureThread = false;
|
|
550
|
+
new Thread(captureThread).start();
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
@Override
|
|
555
|
+
public boolean destroyCapturer() {
|
|
556
|
+
captureLock.lock();
|
|
557
|
+
|
|
558
|
+
// release the object
|
|
559
|
+
if (null != echoCanceler) {
|
|
560
|
+
echoCanceler.release();
|
|
561
|
+
echoCanceler = null;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (null != noiseSuppressor) {
|
|
565
|
+
noiseSuppressor.release();
|
|
566
|
+
noiseSuppressor = null;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
audioRecord.release();
|
|
570
|
+
audioRecord = null;
|
|
571
|
+
shutdownCaptureThread = true;
|
|
572
|
+
captureEvent.signal();
|
|
573
|
+
|
|
574
|
+
captureLock.unlock();
|
|
575
|
+
|
|
576
|
+
unRegisterPhoneStateListener();
|
|
577
|
+
wasCapturing = false;
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
public int getEstimatedCaptureDelay() {
|
|
582
|
+
return estimatedCaptureDelay;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
@Override
|
|
586
|
+
public boolean startCapturer() {
|
|
587
|
+
if (audioRecord == null) {
|
|
588
|
+
throw new IllegalStateException("startCapturer(): startRecording() called on an "
|
|
589
|
+
+ "uninitialized AudioRecord");
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
audioRecord.startRecording();
|
|
593
|
+
|
|
594
|
+
} catch (IllegalStateException e) {
|
|
595
|
+
throw new RuntimeException(e.getMessage());
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
captureLock.lock();
|
|
599
|
+
isCapturing = true;
|
|
600
|
+
captureEvent.signal();
|
|
601
|
+
captureLock.unlock();
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
@Override
|
|
606
|
+
public boolean stopCapturer() {
|
|
607
|
+
|
|
608
|
+
if (audioRecord == null) {
|
|
609
|
+
throw new IllegalStateException("stopCapturer(): stop() called on an uninitialized AudioRecord");
|
|
610
|
+
}
|
|
611
|
+
captureLock.lock();
|
|
612
|
+
try {
|
|
613
|
+
// Only stop if we are recording.
|
|
614
|
+
if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
|
|
615
|
+
audioRecord.stop();
|
|
616
|
+
}
|
|
617
|
+
} catch (Exception e) {
|
|
618
|
+
throw new RuntimeException(e.getMessage());
|
|
619
|
+
} finally {
|
|
620
|
+
// Ensure we always unlock
|
|
621
|
+
isCapturing = false;
|
|
622
|
+
captureLock.unlock();
|
|
623
|
+
}
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
private Runnable captureThread = () -> {
|
|
628
|
+
int samplesToRec = captureSamplingRate / 100;
|
|
629
|
+
int samplesRead;
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
|
|
633
|
+
} catch (Exception e) {
|
|
634
|
+
Log.e(TAG, "android.os.Process.setThreadPriority(): " + e.getMessage());
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
while (!shutdownCaptureThread) {
|
|
638
|
+
captureLock.lock();
|
|
639
|
+
try {
|
|
640
|
+
if (!this.isCapturing) {
|
|
641
|
+
captureEvent.await();
|
|
642
|
+
continue;
|
|
643
|
+
} else {
|
|
644
|
+
if (audioRecord == null) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
int lengthInBytes = (samplesToRec << 1) * NUM_CHANNELS_CAPTURING;
|
|
648
|
+
int readBytes = audioRecord.read(tempBufRec, 0, lengthInBytes);
|
|
649
|
+
if (readBytes >= 0) {
|
|
650
|
+
recBuffer.rewind();
|
|
651
|
+
recBuffer.put(tempBufRec);
|
|
652
|
+
samplesRead = (readBytes >> 1) / NUM_CHANNELS_CAPTURING;
|
|
653
|
+
} else {
|
|
654
|
+
switch (readBytes) {
|
|
655
|
+
case AudioRecord.ERROR_BAD_VALUE:
|
|
656
|
+
throw new RuntimeException("captureThread(): AudioRecord.ERROR_BAD_VALUE");
|
|
657
|
+
case AudioRecord.ERROR_INVALID_OPERATION:
|
|
658
|
+
throw new RuntimeException("captureThread(): AudioRecord.ERROR_INVALID_OPERATION");
|
|
659
|
+
case AudioRecord.ERROR:
|
|
660
|
+
default:
|
|
661
|
+
throw new RuntimeException("captureThread(): AudioRecord.ERROR or default");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
} catch (Exception e) {
|
|
666
|
+
throw new RuntimeException(e.getMessage());
|
|
667
|
+
} finally {
|
|
668
|
+
// Ensure we always unlock
|
|
669
|
+
captureLock.unlock();
|
|
670
|
+
}
|
|
671
|
+
getAudioBus().writeCaptureData(recBuffer, samplesRead);
|
|
672
|
+
estimatedCaptureDelay = samplesRead * 1000 / captureSamplingRate;
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@Override
|
|
678
|
+
public boolean initRenderer() {
|
|
679
|
+
|
|
680
|
+
// Request audio focus for playback
|
|
681
|
+
int result = audioManager.requestAudioFocus(audioFocusChangeListener,
|
|
682
|
+
// Use the music stream.
|
|
683
|
+
AudioManager.STREAM_VOICE_CALL,
|
|
684
|
+
// Request permanent focus.
|
|
685
|
+
AudioManager.AUDIOFOCUS_GAIN);
|
|
686
|
+
|
|
687
|
+
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
|
688
|
+
Log.d("AUDIO_FOCUS", "Audio Focus request GRANTED !");
|
|
689
|
+
} else {
|
|
690
|
+
Log.e("AUDIO_FOCUS", "Audio Focus request DENIED !");
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// initalize default values
|
|
695
|
+
bluetoothState = BluetoothState.Disconnected;
|
|
696
|
+
/* register for bluetooth sco callbacks and attempt to enable it */
|
|
697
|
+
enableBluetoothEvents();
|
|
698
|
+
// get the minimum buffer size that can be used
|
|
699
|
+
int minPlayBufSize = AudioTrack.getMinBufferSize(
|
|
700
|
+
rendererSettings.getSampleRate(),
|
|
701
|
+
NUM_CHANNELS_RENDERING == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO,
|
|
702
|
+
AudioFormat.ENCODING_PCM_16BIT
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
// release the object
|
|
706
|
+
if (audioTrack != null) {
|
|
707
|
+
audioTrack.release();
|
|
708
|
+
audioTrack = null;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
try {
|
|
712
|
+
int channelConfig = (NUM_CHANNELS_RENDERING == 1) ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;
|
|
713
|
+
|
|
714
|
+
audioTrack = new AudioTrack(
|
|
715
|
+
AudioManager.STREAM_VOICE_CALL,
|
|
716
|
+
rendererSettings.getSampleRate(),
|
|
717
|
+
channelConfig,
|
|
718
|
+
AudioFormat.ENCODING_PCM_16BIT,
|
|
719
|
+
minPlayBufSize >= 6000 ? minPlayBufSize : minPlayBufSize * 2,
|
|
720
|
+
AudioTrack.MODE_STREAM
|
|
721
|
+
);
|
|
722
|
+
} catch (Exception e) {
|
|
723
|
+
throw new RuntimeException(e.getMessage());
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Check that the audioRecord is ready to be used.
|
|
727
|
+
if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
|
728
|
+
throw new RuntimeException("Audio renderer not initialized " + rendererSettings.getSampleRate());
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
bufferedPlaySamples = 0;
|
|
732
|
+
|
|
733
|
+
registerPhoneStateListener();
|
|
734
|
+
|
|
735
|
+
shutdownRenderThread = false;
|
|
736
|
+
new Thread(renderThread).start();
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private void destroyAudioTrack() {
|
|
741
|
+
rendererLock.lock();
|
|
742
|
+
audioTrack.release();
|
|
743
|
+
audioTrack = null;
|
|
744
|
+
shutdownRenderThread = true;
|
|
745
|
+
renderEvent.signal();
|
|
746
|
+
rendererLock.unlock();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
@Override
|
|
750
|
+
public boolean destroyRenderer() {
|
|
751
|
+
destroyAudioTrack();
|
|
752
|
+
disableBluetoothEvents();
|
|
753
|
+
unregisterHeadsetReceiver();
|
|
754
|
+
audioManager.setSpeakerphoneOn(false);
|
|
755
|
+
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
|
756
|
+
|
|
757
|
+
unRegisterPhoneStateListener();
|
|
758
|
+
wasRendering = false;
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
public int getEstimatedRenderDelay() {
|
|
763
|
+
return estimatedRenderDelay;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
@Override
|
|
767
|
+
public boolean startRenderer() {
|
|
768
|
+
Log.d("AUDIO_FOCUS", "Start Renderer");
|
|
769
|
+
|
|
770
|
+
// Enable speakerphone unless headset is connected.
|
|
771
|
+
synchronized (bluetoothLock) {
|
|
772
|
+
if (BluetoothState.Connected != bluetoothState) {
|
|
773
|
+
if (audioManager.isWiredHeadsetOn()) {
|
|
774
|
+
Log.d(TAG, "Turn off Speaker phone");
|
|
775
|
+
audioManager.setSpeakerphoneOn(false);
|
|
776
|
+
} else {
|
|
777
|
+
Log.d(TAG, "Turn on Speaker phone");
|
|
778
|
+
if (getOutputType() == OutputType.SPEAKER_PHONE) {
|
|
779
|
+
audioManager.setSpeakerphoneOn(true);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Start playout.
|
|
786
|
+
if (audioTrack == null) {
|
|
787
|
+
throw new IllegalStateException("startRenderer(): play() called on uninitialized AudioTrack");
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
audioTrack.play();
|
|
791
|
+
} catch (IllegalStateException e) {
|
|
792
|
+
throw new RuntimeException(e.getMessage());
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
rendererLock.lock();
|
|
796
|
+
isRendering = true;
|
|
797
|
+
renderEvent.signal();
|
|
798
|
+
rendererLock.unlock();
|
|
799
|
+
registerBtReceiver();
|
|
800
|
+
registerHeadsetReceiver();
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
@Override
|
|
805
|
+
public boolean stopRenderer() {
|
|
806
|
+
Log.d("AUDIO_FOCUS", "Stop Renderer");
|
|
807
|
+
|
|
808
|
+
if (audioTrack == null) {
|
|
809
|
+
throw new IllegalStateException("stopRenderer(): stop() called on uninitialized AudioTrack");
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
rendererLock.lock();
|
|
813
|
+
|
|
814
|
+
try {
|
|
815
|
+
// Only stop if we are playing.
|
|
816
|
+
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
|
|
817
|
+
audioTrack.stop();
|
|
818
|
+
|
|
819
|
+
}
|
|
820
|
+
audioTrack.flush();
|
|
821
|
+
} catch (Exception e) {
|
|
822
|
+
throw new RuntimeException(e.getMessage());
|
|
823
|
+
} finally {
|
|
824
|
+
isRendering = false;
|
|
825
|
+
rendererLock.unlock();
|
|
826
|
+
}
|
|
827
|
+
audioManagerMode.releaseMode(audioManager);
|
|
828
|
+
|
|
829
|
+
unregisterHeadsetReceiver();
|
|
830
|
+
unregisterBtReceiver();
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private Runnable renderThread = () -> {
|
|
835
|
+
int samplesToPlay = samplesPerBuffer;
|
|
836
|
+
try {
|
|
837
|
+
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
|
|
838
|
+
} catch (Exception e) {
|
|
839
|
+
Log.e(TAG, "android.os.Process.setThreadPriority(): " + e.getMessage());
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
while (!shutdownRenderThread) {
|
|
843
|
+
rendererLock.lock();
|
|
844
|
+
try {
|
|
845
|
+
if (!this.isRendering) {
|
|
846
|
+
renderEvent.await();
|
|
847
|
+
continue;
|
|
848
|
+
|
|
849
|
+
} else {
|
|
850
|
+
rendererLock.unlock();
|
|
851
|
+
|
|
852
|
+
// Don't lock on audioBus calls
|
|
853
|
+
playBuffer.clear();
|
|
854
|
+
int samplesRead = getAudioBus().readRenderData(playBuffer, samplesToPlay);
|
|
855
|
+
|
|
856
|
+
rendererLock.lock();
|
|
857
|
+
|
|
858
|
+
// After acquiring the lock again we must check if we are still playing
|
|
859
|
+
if (audioTrack == null || !this.isRendering) {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
int bytesRead = (samplesRead << 1) * NUM_CHANNELS_RENDERING;
|
|
864
|
+
playBuffer.get(tempBufPlay, 0, bytesRead);
|
|
865
|
+
|
|
866
|
+
int bytesWritten = audioTrack.write(tempBufPlay, 0, bytesRead);
|
|
867
|
+
|
|
868
|
+
if (bytesWritten > 0) {
|
|
869
|
+
// increase by number of written samples
|
|
870
|
+
bufferedPlaySamples += (bytesWritten >> 1) / NUM_CHANNELS_RENDERING;
|
|
871
|
+
|
|
872
|
+
// decrease by number of played samples
|
|
873
|
+
int pos = audioTrack.getPlaybackHeadPosition();
|
|
874
|
+
|
|
875
|
+
if (pos < playPosition) {
|
|
876
|
+
// wrap or reset by driver
|
|
877
|
+
playPosition = 0;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
bufferedPlaySamples -= (pos - playPosition);
|
|
881
|
+
playPosition = pos;
|
|
882
|
+
|
|
883
|
+
// we calculate the estimated delay based on the buffered samples
|
|
884
|
+
estimatedRenderDelay = bufferedPlaySamples * 1000 / outputSamplingRate;
|
|
885
|
+
} else {
|
|
886
|
+
switch (bytesWritten) {
|
|
887
|
+
case AudioTrack.ERROR_BAD_VALUE:
|
|
888
|
+
throw new RuntimeException("renderThread(): AudioTrack.ERROR_BAD_VALUE");
|
|
889
|
+
case AudioTrack.ERROR_INVALID_OPERATION:
|
|
890
|
+
throw new RuntimeException("renderThread(): AudioTrack.ERROR_INVALID_OPERATION");
|
|
891
|
+
case AudioTrack.ERROR:
|
|
892
|
+
default:
|
|
893
|
+
throw new RuntimeException(
|
|
894
|
+
"renderThread(): AudioTrack.ERROR or default");
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
} catch (Exception e) {
|
|
899
|
+
throw new RuntimeException(e.getMessage());
|
|
900
|
+
} finally {
|
|
901
|
+
rendererLock.unlock();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
@Override
|
|
907
|
+
public AudioSettings getCaptureSettings() {
|
|
908
|
+
return this.captureSettings;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
@Override
|
|
912
|
+
public AudioSettings getRenderSettings() {
|
|
913
|
+
return this.rendererSettings;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/*
|
|
917
|
+
* Communication modes handling.
|
|
918
|
+
*/
|
|
919
|
+
public boolean setOutputMode(OutputMode mode) {
|
|
920
|
+
//This is public API and also called during initialization
|
|
921
|
+
Log.d("AUDIO_FOCUS", "outputmode set to : " + mode);
|
|
922
|
+
super.setOutputMode(mode);
|
|
923
|
+
|
|
924
|
+
if(OutputMode.SpeakerPhone == mode) {
|
|
925
|
+
audioState.setLastOutputType(getOutputType());
|
|
926
|
+
setOutputType(OutputType.SPEAKER_PHONE);
|
|
927
|
+
audioManager.setSpeakerphoneOn(true);
|
|
928
|
+
stopBluetoothSco();
|
|
929
|
+
audioManager.setBluetoothScoOn(false);
|
|
930
|
+
} else {
|
|
931
|
+
if (audioState.getLastOutputType() == OutputType.BLUETOOTH || bluetoothState == BluetoothState.Connected) {
|
|
932
|
+
connectBluetooth();
|
|
933
|
+
} else {
|
|
934
|
+
audioState.setLastOutputType(getOutputType());
|
|
935
|
+
audioManager.setSpeakerphoneOn(false);
|
|
936
|
+
setOutputType(OutputType.EAR_PIECE);
|
|
937
|
+
stopBluetoothSco();
|
|
938
|
+
audioManager.setBluetoothScoOn(false);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
private boolean isHeadsetReceiverRegistered;
|
|
946
|
+
|
|
947
|
+
private void registerHeadsetReceiver() {
|
|
948
|
+
Log.d(TAG, "registerHeadsetReceiver() called ... isHeadsetReceiverRegistered = " + isHeadsetReceiverRegistered);
|
|
949
|
+
|
|
950
|
+
if (isHeadsetReceiverRegistered) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
context.registerReceiver(headsetBroadcastReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
|
|
955
|
+
isHeadsetReceiverRegistered = true;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
private void unregisterHeadsetReceiver() {
|
|
959
|
+
Log.d(TAG, "unregisterHeadsetReceiver() called .. isHeadsetReceiverRegistered = " + isHeadsetReceiverRegistered);
|
|
960
|
+
|
|
961
|
+
if (!isHeadsetReceiverRegistered) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
context.unregisterReceiver(headsetBroadcastReceiver);
|
|
966
|
+
isHeadsetReceiverRegistered = false;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
private boolean isBluetoothHeadSetReceiverRegistered;
|
|
970
|
+
|
|
971
|
+
private void registerBtReceiver() {
|
|
972
|
+
Log.d(TAG, "registerBtReceiver() called .. isBluetoothHeadSetReceiverRegistered = " + isBluetoothHeadSetReceiverRegistered);
|
|
973
|
+
|
|
974
|
+
if (isBluetoothHeadSetReceiverRegistered) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
IntentFilter btFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
|
979
|
+
btFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
|
|
980
|
+
context.registerReceiver(bluetoothBroadcastReceiver, btFilter);
|
|
981
|
+
|
|
982
|
+
// Register receiver for change in audio connection state of the Headset profile.
|
|
983
|
+
context.registerReceiver(bluetoothHeadsetReceiver, new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));
|
|
984
|
+
|
|
985
|
+
isBluetoothHeadSetReceiverRegistered = true;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
private void unregisterBtReceiver() {
|
|
990
|
+
Log.d(TAG, "unregisterBtReceiver() called .. bluetoothHeadSetReceiverRegistered = " + isBluetoothHeadSetReceiverRegistered);
|
|
991
|
+
|
|
992
|
+
if (!isBluetoothHeadSetReceiverRegistered) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
context.unregisterReceiver(bluetoothBroadcastReceiver);
|
|
997
|
+
context.unregisterReceiver(bluetoothHeadsetReceiver);
|
|
998
|
+
isBluetoothHeadSetReceiverRegistered = false;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
private boolean isPhoneStateListenerRegistered;
|
|
1002
|
+
|
|
1003
|
+
private void registerPhoneStateListener() {
|
|
1004
|
+
Log.d(TAG, "registerPhoneStateListener() called");
|
|
1005
|
+
|
|
1006
|
+
if (isPhoneStateListenerRegistered) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
if (telephonyManager != null) {
|
|
1011
|
+
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
|
1012
|
+
isPhoneStateListenerRegistered = true;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
private void unRegisterPhoneStateListener() {
|
|
1017
|
+
Log.d(TAG, "unRegisterPhoneStateListener() called");
|
|
1018
|
+
|
|
1019
|
+
if (!isPhoneStateListenerRegistered) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
if (telephonyManager != null) {
|
|
1024
|
+
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
|
1025
|
+
isPhoneStateListenerRegistered = false;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
@Override
|
|
1030
|
+
public synchronized void onPause() {
|
|
1031
|
+
audioState.setLastOutputType(getOutputType());
|
|
1032
|
+
unregisterBtReceiver();
|
|
1033
|
+
unregisterHeadsetReceiver();
|
|
1034
|
+
isPaused = true;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
@Override
|
|
1038
|
+
public synchronized void onResume() {
|
|
1039
|
+
Log.d(TAG, "onResume() called");
|
|
1040
|
+
if (!isPaused) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
if (bluetoothState == BluetoothState.Disconnected) {
|
|
1045
|
+
if (isRendering && (audioState.getLastOutputType() == OutputType.SPEAKER_PHONE)) {
|
|
1046
|
+
if (!audioManager.isWiredHeadsetOn()) {
|
|
1047
|
+
Log.d(TAG, "onResume() - Set Speaker Phone ON True");
|
|
1048
|
+
audioManager.setSpeakerphoneOn(true);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/* register handler for phonejack notifications */
|
|
1054
|
+
registerBtReceiver();
|
|
1055
|
+
registerHeadsetReceiver();
|
|
1056
|
+
connectBluetooth();
|
|
1057
|
+
forceInvokeConnectBluetooth();
|
|
1058
|
+
|
|
1059
|
+
isPaused = false;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
@Override
|
|
1063
|
+
public BluetoothState getBluetoothState() {
|
|
1064
|
+
return bluetoothState;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
private void enableBluetoothEvents() {
|
|
1068
|
+
if (audioManager.isBluetoothScoAvailableOffCall()) {
|
|
1069
|
+
registerBtReceiver();
|
|
1070
|
+
connectBluetooth();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
private void disableBluetoothEvents() {
|
|
1076
|
+
if (null != bluetoothProfile && bluetoothAdapter != null) {
|
|
1077
|
+
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothProfile);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
unregisterBtReceiver();
|
|
1081
|
+
|
|
1082
|
+
// Force a shutdown of bluetooth: when a call comes in, the handler is not invoked by system.
|
|
1083
|
+
Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
|
1084
|
+
intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
|
|
1085
|
+
bluetoothBroadcastReceiver.onReceive(context, intent);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
private void startBluetoothSco() {
|
|
1089
|
+
try {
|
|
1090
|
+
audioManager.startBluetoothSco();
|
|
1091
|
+
} catch (NullPointerException e) {
|
|
1092
|
+
Log.d(TAG, e.getMessage());
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
private void stopBluetoothSco() {
|
|
1097
|
+
try {
|
|
1098
|
+
audioManager.stopBluetoothSco();
|
|
1099
|
+
} catch (NullPointerException e) {
|
|
1100
|
+
Log.d(TAG, e.getMessage());
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private final BluetoothProfile.ServiceListener bluetoothProfileServiceListener = new BluetoothProfile.ServiceListener() {
|
|
1105
|
+
@Override
|
|
1106
|
+
public void onServiceConnected(int type, BluetoothProfile profile) {
|
|
1107
|
+
Log.d(TAG, "BluetoothProfile.ServiceListener.onServiceConnected()");
|
|
1108
|
+
if (BluetoothProfile.HEADSET == type) {
|
|
1109
|
+
bluetoothProfile = profile;
|
|
1110
|
+
List<BluetoothDevice> devices = profile.getConnectedDevices();
|
|
1111
|
+
|
|
1112
|
+
Log.d(TAG, "Service Proxy Connected");
|
|
1113
|
+
|
|
1114
|
+
if (!devices.isEmpty() &&
|
|
1115
|
+
BluetoothHeadset.STATE_CONNECTED == profile.getConnectionState(devices.get(0))) {
|
|
1116
|
+
// Force a init of bluetooth: the handler will not send a connected event if a
|
|
1117
|
+
// device is already connected at the time of proxy connection request.
|
|
1118
|
+
Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
|
1119
|
+
intent.putExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_CONNECTED);
|
|
1120
|
+
bluetoothBroadcastReceiver.onReceive(context, intent);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
@Override
|
|
1126
|
+
public void onServiceDisconnected(int type) {
|
|
1127
|
+
Log.d(TAG, "BluetoothProfile.ServiceListener.onServiceDisconnected()");
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
private void forceInvokeConnectBluetooth() {
|
|
1132
|
+
Log.d(TAG, "forceConnectBluetooth() called");
|
|
1133
|
+
|
|
1134
|
+
// Force reconnection of bluetooth in the event of a phone call.
|
|
1135
|
+
synchronized (bluetoothLock) {
|
|
1136
|
+
bluetoothState = BluetoothState.Disconnected;
|
|
1137
|
+
if (bluetoothAdapter != null) {
|
|
1138
|
+
bluetoothAdapter.getProfileProxy(
|
|
1139
|
+
context,
|
|
1140
|
+
bluetoothProfileServiceListener,
|
|
1141
|
+
BluetoothProfile.HEADSET
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|