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.
Files changed (150) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +190 -0
  3. package/android/build.gradle +104 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +3 -0
  6. package/android/src/main/AndroidManifestNew.xml +2 -0
  7. package/android/src/main/java/com/altibbi/AltibbiModule.kt +18 -0
  8. package/android/src/main/java/com/altibbi/AltibbiPackage.kt +18 -0
  9. package/android/src/main/java/com/altibbi/OTCustomAudioDevice.java +1146 -0
  10. package/android/src/main/java/com/altibbi/OTPublisherLayout.java +61 -0
  11. package/android/src/main/java/com/altibbi/OTPublisherViewManager.java +30 -0
  12. package/android/src/main/java/com/altibbi/OTRN.java +101 -0
  13. package/android/src/main/java/com/altibbi/OTScreenCapturer.java +120 -0
  14. package/android/src/main/java/com/altibbi/OTSessionManager.java +1281 -0
  15. package/android/src/main/java/com/altibbi/OTSubscriberLayout.java +68 -0
  16. package/android/src/main/java/com/altibbi/OTSubscriberViewManager.java +30 -0
  17. package/android/src/main/java/com/altibbi/Socket.kt +294 -0
  18. package/android/src/main/java/com/altibbi/SocketEventEmitter.kt +25 -0
  19. package/android/src/main/java/com/altibbi/utils/EventUtils.java +189 -0
  20. package/android/src/main/java/com/altibbi/utils/Utils.java +135 -0
  21. package/ios/Altibbi-Bridging-Header.h +6 -0
  22. package/ios/Altibbi.mm +10 -0
  23. package/ios/Altibbi.swift +4 -0
  24. package/ios/OTCustomAudioDriver.swift +696 -0
  25. package/ios/OTPublisher.m +16 -0
  26. package/ios/OTPublisherManager.swift +21 -0
  27. package/ios/OTPublisherView.swift +28 -0
  28. package/ios/OTRN.swift +27 -0
  29. package/ios/OTScreenCapture.h +27 -0
  30. package/ios/OTScreenCapture.m +171 -0
  31. package/ios/OTSessionManager.m +127 -0
  32. package/ios/OTSessionManager.swift +866 -0
  33. package/ios/OTSubscriber.m +15 -0
  34. package/ios/OTSubscriberManager.swift +21 -0
  35. package/ios/OTSubscriberView.swift +29 -0
  36. package/ios/OpenTokReactNative.h +13 -0
  37. package/ios/OpenTokReactNative.m +13 -0
  38. package/ios/SocketReactNative.m +38 -0
  39. package/ios/SocketReactNative.swift +276 -0
  40. package/ios/Utils/EventUtils.swift +143 -0
  41. package/ios/Utils/Utils.swift +126 -0
  42. package/lib/commonjs/connection.js +200 -0
  43. package/lib/commonjs/connection.js.map +1 -0
  44. package/lib/commonjs/data.js +12 -0
  45. package/lib/commonjs/data.js.map +1 -0
  46. package/lib/commonjs/index.js +275 -0
  47. package/lib/commonjs/index.js.map +1 -0
  48. package/lib/commonjs/scoket.js +245 -0
  49. package/lib/commonjs/scoket.js.map +1 -0
  50. package/lib/commonjs/service.js +21 -0
  51. package/lib/commonjs/service.js.map +1 -0
  52. package/lib/commonjs/types.js +2 -0
  53. package/lib/commonjs/types.js.map +1 -0
  54. package/lib/commonjs/video/OT.js +57 -0
  55. package/lib/commonjs/video/OT.js.map +1 -0
  56. package/lib/commonjs/video/OTError.js +17 -0
  57. package/lib/commonjs/video/OTError.js.map +1 -0
  58. package/lib/commonjs/video/OTPublisher.js +171 -0
  59. package/lib/commonjs/video/OTPublisher.js.map +1 -0
  60. package/lib/commonjs/video/OTSession.js +205 -0
  61. package/lib/commonjs/video/OTSession.js.map +1 -0
  62. package/lib/commonjs/video/OTSubscriber.js +185 -0
  63. package/lib/commonjs/video/OTSubscriber.js.map +1 -0
  64. package/lib/commonjs/video/contexts/OTContext.js +11 -0
  65. package/lib/commonjs/video/contexts/OTContext.js.map +1 -0
  66. package/lib/commonjs/video/helpers/OTHelper.js +92 -0
  67. package/lib/commonjs/video/helpers/OTHelper.js.map +1 -0
  68. package/lib/commonjs/video/helpers/OTPublisherHelper.js +117 -0
  69. package/lib/commonjs/video/helpers/OTPublisherHelper.js.map +1 -0
  70. package/lib/commonjs/video/helpers/OTSessionHelper.js +206 -0
  71. package/lib/commonjs/video/helpers/OTSessionHelper.js.map +1 -0
  72. package/lib/commonjs/video/helpers/OTSubscriberHelper.js +121 -0
  73. package/lib/commonjs/video/helpers/OTSubscriberHelper.js.map +1 -0
  74. package/lib/commonjs/video/index.js +42 -0
  75. package/lib/commonjs/video/index.js.map +1 -0
  76. package/lib/commonjs/video/views/OTPublisherView.js +26 -0
  77. package/lib/commonjs/video/views/OTPublisherView.js.map +1 -0
  78. package/lib/commonjs/video/views/OTSubscriberView.js +25 -0
  79. package/lib/commonjs/video/views/OTSubscriberView.js.map +1 -0
  80. package/lib/module/connection.js +180 -0
  81. package/lib/module/connection.js.map +1 -0
  82. package/lib/module/data.js +6 -0
  83. package/lib/module/data.js.map +1 -0
  84. package/lib/module/index.js +12 -0
  85. package/lib/module/index.js.map +1 -0
  86. package/lib/module/scoket.js +235 -0
  87. package/lib/module/scoket.js.map +1 -0
  88. package/lib/module/service.js +14 -0
  89. package/lib/module/service.js.map +1 -0
  90. package/lib/module/types.js +2 -0
  91. package/lib/module/types.js.map +1 -0
  92. package/lib/module/video/OT.js +49 -0
  93. package/lib/module/video/OT.js.map +1 -0
  94. package/lib/module/video/OTError.js +10 -0
  95. package/lib/module/video/OTError.js.map +1 -0
  96. package/lib/module/video/OTPublisher.js +162 -0
  97. package/lib/module/video/OTPublisher.js.map +1 -0
  98. package/lib/module/video/OTSession.js +195 -0
  99. package/lib/module/video/OTSession.js.map +1 -0
  100. package/lib/module/video/OTSubscriber.js +175 -0
  101. package/lib/module/video/OTSubscriber.js.map +1 -0
  102. package/lib/module/video/contexts/OTContext.js +4 -0
  103. package/lib/module/video/contexts/OTContext.js.map +1 -0
  104. package/lib/module/video/helpers/OTHelper.js +82 -0
  105. package/lib/module/video/helpers/OTHelper.js.map +1 -0
  106. package/lib/module/video/helpers/OTPublisherHelper.js +110 -0
  107. package/lib/module/video/helpers/OTPublisherHelper.js.map +1 -0
  108. package/lib/module/video/helpers/OTSessionHelper.js +195 -0
  109. package/lib/module/video/helpers/OTSessionHelper.js.map +1 -0
  110. package/lib/module/video/helpers/OTSubscriberHelper.js +112 -0
  111. package/lib/module/video/helpers/OTSubscriberHelper.js.map +1 -0
  112. package/lib/module/video/index.js +7 -0
  113. package/lib/module/video/index.js.map +1 -0
  114. package/lib/module/video/views/OTPublisherView.js +18 -0
  115. package/lib/module/video/views/OTPublisherView.js.map +1 -0
  116. package/lib/module/video/views/OTSubscriberView.js +17 -0
  117. package/lib/module/video/views/OTSubscriberView.js.map +1 -0
  118. package/lib/typescript/src/connection.d.ts +40 -0
  119. package/lib/typescript/src/connection.d.ts.map +1 -0
  120. package/lib/typescript/src/data.d.ts +7 -0
  121. package/lib/typescript/src/data.d.ts.map +1 -0
  122. package/lib/typescript/src/index.d.ts +12 -0
  123. package/lib/typescript/src/index.d.ts.map +1 -0
  124. package/lib/typescript/src/scoket.d.ts +100 -0
  125. package/lib/typescript/src/scoket.d.ts.map +1 -0
  126. package/lib/typescript/src/service.d.ts +9 -0
  127. package/lib/typescript/src/service.d.ts.map +1 -0
  128. package/lib/typescript/src/types.d.ts +22 -0
  129. package/lib/typescript/src/types.d.ts.map +1 -0
  130. package/package.json +178 -0
  131. package/react-native-altibbi.podspec +46 -0
  132. package/src/connection.ts +255 -0
  133. package/src/data.ts +21 -0
  134. package/src/index.tsx +80 -0
  135. package/src/scoket.ts +365 -0
  136. package/src/service.ts +20 -0
  137. package/src/types.ts +22 -0
  138. package/src/video/OT.js +65 -0
  139. package/src/video/OTError.js +14 -0
  140. package/src/video/OTPublisher.js +193 -0
  141. package/src/video/OTSession.js +168 -0
  142. package/src/video/OTSubscriber.js +148 -0
  143. package/src/video/contexts/OTContext.js +5 -0
  144. package/src/video/helpers/OTHelper.js +91 -0
  145. package/src/video/helpers/OTPublisherHelper.js +122 -0
  146. package/src/video/helpers/OTSessionHelper.js +233 -0
  147. package/src/video/helpers/OTSubscriberHelper.js +125 -0
  148. package/src/video/index.js +13 -0
  149. package/src/video/views/OTPublisherView.js +19 -0
  150. 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
+ }