@cloudflare/realtimekit-react-native 0.2.1 → 0.2.2-staging.1
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/android/src/main/java/com/cloudflare/realtimekit/CoreModule.java +19 -0
- package/android/src/main/java/com/cloudflare/realtimekit/ForegroundService.java +2 -4
- package/android/src/main/java/com/cloudflare/realtimekit/RTKHelperModule.java +1 -2
- package/android/src/main/java/com/cloudflare/realtimekit/RTKLogger.java +116 -0
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/AppRTCBluetoothManager.java +75 -74
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/AppRTCUtils.java +10 -9
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallManagerModule.java +175 -872
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallProximityManager.java +9 -8
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallWakeLockUtils.java +8 -7
- package/ios/BroadcastEventEmitter.m +9 -0
- package/ios/Core.h +9 -1
- package/ios/Core.m +42 -0
- package/ios/RNInCallManager.m +639 -700
- package/ios/RTKLogger.h +51 -0
- package/ios/RTKLogger.m +107 -0
- package/ios/RTKRNBackgroundTimer.m +8 -7
- package/ios/RTKRNPermissionHandlerNotifications.m +12 -0
- package/ios/RTKRNPermissions.m +24 -1
- package/ios/RTKScreensharePickerView.m +6 -0
- package/ios/screenshare/RTKScreenshareHandler.swift +1 -1
- package/ios/screenshare/RTKScreenshareUploader.swift +3 -3
- package/ios/screenshare/RTKSocketConnection.swift +8 -8
- package/lib/commonjs/LocalMediaHandler.js +82 -24
- package/lib/commonjs/LocalMediaHandler.js.map +1 -1
- package/lib/commonjs/LocalMediaUtils.js +60 -5
- package/lib/commonjs/LocalMediaUtils.js.map +1 -1
- package/lib/commonjs/NativeAudioManager.js +7 -3
- package/lib/commonjs/NativeAudioManager.js.map +1 -1
- package/lib/commonjs/utils/version.js +1 -1
- package/lib/commonjs/utils/version.js.map +1 -1
- package/lib/module/LocalMediaHandler.js +82 -24
- package/lib/module/LocalMediaHandler.js.map +1 -1
- package/lib/module/LocalMediaUtils.js +60 -5
- package/lib/module/LocalMediaUtils.js.map +1 -1
- package/lib/module/NativeAudioManager.js +7 -3
- package/lib/module/NativeAudioManager.js.map +1 -1
- package/lib/module/utils/version.js +1 -1
- package/lib/module/utils/version.js.map +1 -1
- package/lib/typescript/LocalMediaHandler.d.ts +1 -0
- package/lib/typescript/LocalMediaUtils.d.ts +1 -0
- package/lib/typescript/NativeAudioManager.d.ts +1 -0
- package/lib/typescript/utils/version.d.ts +1 -1
- package/package.json +2 -4
package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallManagerModule.java
CHANGED
|
@@ -10,27 +10,19 @@ import android.media.AudioAttributes;
|
|
|
10
10
|
import android.media.AudioDeviceInfo;
|
|
11
11
|
import android.media.AudioFocusRequest;
|
|
12
12
|
import android.media.AudioManager;
|
|
13
|
-
import android.media.MediaPlayer;
|
|
14
|
-
import android.media.ToneGenerator;
|
|
15
|
-
import android.net.Uri;
|
|
16
13
|
import android.os.PowerManager;
|
|
17
14
|
import android.os.Build;
|
|
18
15
|
import android.os.Handler;
|
|
19
16
|
import android.os.Looper;
|
|
20
|
-
import android.provider.Settings;
|
|
21
17
|
import androidx.annotation.Nullable;
|
|
22
18
|
import android.util.Log;
|
|
23
|
-
import android.util.SparseArray;
|
|
24
19
|
import android.view.Display;
|
|
25
20
|
import android.view.KeyEvent;
|
|
26
21
|
import android.view.Window;
|
|
27
22
|
import android.view.WindowManager;
|
|
28
|
-
import android.media.AudioDeviceInfo;
|
|
29
|
-
import android.media.MediaPlayer;
|
|
30
23
|
|
|
31
24
|
import com.facebook.react.bridge.Arguments;
|
|
32
25
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
33
|
-
import com.facebook.react.bridge.NativeModule;
|
|
34
26
|
import com.facebook.react.bridge.Promise;
|
|
35
27
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
36
28
|
import com.facebook.react.bridge.ReactContext;
|
|
@@ -40,16 +32,11 @@ import com.facebook.react.bridge.UiThreadUtil;
|
|
|
40
32
|
import com.facebook.react.bridge.WritableMap;
|
|
41
33
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
42
34
|
|
|
43
|
-
import java.lang.Runnable;
|
|
44
|
-
import java.io.File;
|
|
45
35
|
import java.util.Collections;
|
|
46
|
-
import java.util.Map;
|
|
47
|
-
import java.util.HashMap;
|
|
48
36
|
import java.util.HashSet;
|
|
49
|
-
import java.util.Random;
|
|
50
37
|
import java.util.Set;
|
|
51
|
-
import java.util.List;
|
|
52
38
|
|
|
39
|
+
import com.cloudflare.realtimekit.RTKLogger;
|
|
53
40
|
import com.cloudflare.realtimekit.incallmanager.AppRTCBluetoothManager;
|
|
54
41
|
import com.cloudflare.realtimekit.incallmanager.InCallManagerModule.AudioDevice;
|
|
55
42
|
|
|
@@ -72,7 +59,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
72
59
|
private boolean origIsSpeakerPhoneOn = false;
|
|
73
60
|
private boolean origIsMicrophoneMute = false;
|
|
74
61
|
private int origAudioMode = AudioManager.MODE_INVALID;
|
|
75
|
-
private boolean defaultSpeakerOn =
|
|
62
|
+
private boolean defaultSpeakerOn = true; // Default to video call mode (speaker on)
|
|
76
63
|
private int defaultAudioMode = AudioManager.MODE_IN_COMMUNICATION;
|
|
77
64
|
private int forceSpeakerOn = 0;
|
|
78
65
|
private boolean automatic = true;
|
|
@@ -85,25 +72,6 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
85
72
|
private AudioAttributes mAudioAttributes;
|
|
86
73
|
private AudioFocusRequest mAudioFocusRequest;
|
|
87
74
|
|
|
88
|
-
// --- same as: RingtoneManager.getActualDefaultRingtoneUri(reactContext, RingtoneManager.TYPE_RINGTONE);
|
|
89
|
-
private Uri defaultRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
|
|
90
|
-
private Uri defaultRingbackUri = Settings.System.DEFAULT_RINGTONE_URI;
|
|
91
|
-
private Uri defaultBusytoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
|
|
92
|
-
//private Uri defaultAlarmAlertUri = Settings.System.DEFAULT_ALARM_ALERT_URI; // --- too annoying
|
|
93
|
-
private Uri bundleRingtoneUri;
|
|
94
|
-
private Uri bundleRingbackUri;
|
|
95
|
-
private Uri bundleBusytoneUri;
|
|
96
|
-
private Map<String, Uri> audioUriMap;
|
|
97
|
-
private MyPlayerInterface mRingtone;
|
|
98
|
-
private MyPlayerInterface mRingback;
|
|
99
|
-
private MyPlayerInterface mBusytone;
|
|
100
|
-
private Handler mRingtoneCountDownHandler;
|
|
101
|
-
private String media = "audio";
|
|
102
|
-
|
|
103
|
-
private static final String SPEAKERPHONE_AUTO = "auto";
|
|
104
|
-
private static final String SPEAKERPHONE_TRUE = "true";
|
|
105
|
-
private static final String SPEAKERPHONE_FALSE = "false";
|
|
106
|
-
|
|
107
75
|
/**
|
|
108
76
|
* AudioDevice is the names of possible audio devices that we currently
|
|
109
77
|
* support.
|
|
@@ -140,7 +108,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
140
108
|
private AudioDevice userSelectedAudioDevice;
|
|
141
109
|
|
|
142
110
|
// Contains speakerphone setting: auto, true or false
|
|
143
|
-
private final String useSpeakerphone = SPEAKERPHONE_AUTO;
|
|
111
|
+
private final String useSpeakerphone = "SPEAKERPHONE_AUTO";
|
|
144
112
|
|
|
145
113
|
// Handles all tasks related to Bluetooth headset devices.
|
|
146
114
|
private AppRTCBluetoothManager bluetoothManager = null;
|
|
@@ -153,12 +121,6 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
153
121
|
// avoid duplicate elements.
|
|
154
122
|
private Set<AudioDevice> audioDevices = new HashSet<>();
|
|
155
123
|
|
|
156
|
-
interface MyPlayerInterface {
|
|
157
|
-
public boolean isPlaying();
|
|
158
|
-
public void startPlay(Map<String, Object> data);
|
|
159
|
-
public void stopPlay();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
124
|
@Override
|
|
163
125
|
public String getName() {
|
|
164
126
|
return REACT_NATIVE_MODULE_NAME;
|
|
@@ -171,13 +133,6 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
171
133
|
mWindowManager = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE);
|
|
172
134
|
mPowerManager = (PowerManager) reactContext.getSystemService(Context.POWER_SERVICE);
|
|
173
135
|
audioManager = ((AudioManager) reactContext.getSystemService(Context.AUDIO_SERVICE));
|
|
174
|
-
audioUriMap = new HashMap<String, Uri>();
|
|
175
|
-
audioUriMap.put("defaultRingtoneUri", defaultRingtoneUri);
|
|
176
|
-
audioUriMap.put("defaultRingbackUri", defaultRingbackUri);
|
|
177
|
-
audioUriMap.put("defaultBusytoneUri", defaultBusytoneUri);
|
|
178
|
-
audioUriMap.put("bundleRingtoneUri", bundleRingtoneUri);
|
|
179
|
-
audioUriMap.put("bundleRingbackUri", bundleRingbackUri);
|
|
180
|
-
audioUriMap.put("bundleBusytoneUri", bundleBusytoneUri);
|
|
181
136
|
wakeLockUtils = new InCallWakeLockUtils(reactContext);
|
|
182
137
|
proximityManager = InCallProximityManager.create(reactContext, this);
|
|
183
138
|
|
|
@@ -185,16 +140,16 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
185
140
|
bluetoothManager = AppRTCBluetoothManager.create(reactContext, this);
|
|
186
141
|
});
|
|
187
142
|
|
|
188
|
-
|
|
143
|
+
RTKLogger.d("InCallManager", "InCallManager initialized");
|
|
189
144
|
}
|
|
190
145
|
|
|
191
146
|
private void manualTurnScreenOff() {
|
|
192
|
-
|
|
147
|
+
RTKLogger.d("InCallManager", "manualTurnScreenOff()");
|
|
193
148
|
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
194
149
|
public void run() {
|
|
195
150
|
Activity mCurrentActivity = getCurrentActivity();
|
|
196
151
|
if (mCurrentActivity == null) {
|
|
197
|
-
|
|
152
|
+
RTKLogger.d("InCallManager", "ReactContext doesn't have any Activity attached.");
|
|
198
153
|
return;
|
|
199
154
|
}
|
|
200
155
|
Window window = mCurrentActivity.getWindow();
|
|
@@ -208,12 +163,12 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
208
163
|
}
|
|
209
164
|
|
|
210
165
|
private void manualTurnScreenOn() {
|
|
211
|
-
|
|
166
|
+
RTKLogger.d("InCallManager", "manualTurnScreenOn()");
|
|
212
167
|
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
213
168
|
public void run() {
|
|
214
169
|
Activity mCurrentActivity = getCurrentActivity();
|
|
215
170
|
if (mCurrentActivity == null) {
|
|
216
|
-
|
|
171
|
+
RTKLogger.d("InCallManager", "ReactContext doesn't have any Activity attached.");
|
|
217
172
|
return;
|
|
218
173
|
}
|
|
219
174
|
Window window = mCurrentActivity.getWindow();
|
|
@@ -230,7 +185,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
230
185
|
}
|
|
231
186
|
|
|
232
187
|
private void storeOriginalAudioSetup() {
|
|
233
|
-
|
|
188
|
+
RTKLogger.d("InCallManager", "storeOriginalAudioSetup()");
|
|
234
189
|
if (!isOrigAudioSetupStored) {
|
|
235
190
|
origAudioMode = audioManager.getMode();
|
|
236
191
|
origIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
|
|
@@ -240,7 +195,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
240
195
|
}
|
|
241
196
|
|
|
242
197
|
private void restoreOriginalAudioSetup() {
|
|
243
|
-
|
|
198
|
+
RTKLogger.d("InCallManager", "restoreOriginalAudioSetup()");
|
|
244
199
|
if (isOrigAudioSetupStored) {
|
|
245
200
|
setSpeakerphoneOn(origIsSpeakerPhoneOn);
|
|
246
201
|
setMicrophoneMute(origIsMicrophoneMute);
|
|
@@ -254,7 +209,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
254
209
|
|
|
255
210
|
private void startWiredHeadsetEvent() {
|
|
256
211
|
if (wiredHeadsetReceiver == null) {
|
|
257
|
-
|
|
212
|
+
RTKLogger.d("InCallManager", "startWiredHeadsetEvent()");
|
|
258
213
|
IntentFilter filter = new IntentFilter(ACTION_HEADSET_PLUG);
|
|
259
214
|
wiredHeadsetReceiver = new BroadcastReceiver() {
|
|
260
215
|
@Override
|
|
@@ -270,7 +225,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
270
225
|
data.putBoolean("isPlugged", (intent.getIntExtra("state", 0) == 1) ? true : false);
|
|
271
226
|
data.putBoolean("hasMic", (intent.getIntExtra("microphone", 0) == 1) ? true : false);
|
|
272
227
|
data.putString("deviceName", deviceName);
|
|
273
|
-
sendEvent("
|
|
228
|
+
sendEvent("onAudioDeviceChanged", null);
|
|
274
229
|
} else {
|
|
275
230
|
hasWiredHeadset = false;
|
|
276
231
|
}
|
|
@@ -280,14 +235,14 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
280
235
|
if (reactContext != null) {
|
|
281
236
|
this.registerReceiver(wiredHeadsetReceiver, filter);
|
|
282
237
|
} else {
|
|
283
|
-
|
|
238
|
+
RTKLogger.w("InCallManager", "startWiredHeadsetEvent() reactContext is null");
|
|
284
239
|
}
|
|
285
240
|
}
|
|
286
241
|
}
|
|
287
242
|
|
|
288
243
|
private void stopWiredHeadsetEvent() {
|
|
289
244
|
if (wiredHeadsetReceiver != null) {
|
|
290
|
-
|
|
245
|
+
RTKLogger.d("InCallManager", "stopWiredHeadsetEvent()");
|
|
291
246
|
this.unregisterReceiver(this.wiredHeadsetReceiver);
|
|
292
247
|
wiredHeadsetReceiver = null;
|
|
293
248
|
}
|
|
@@ -295,7 +250,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
295
250
|
|
|
296
251
|
private void startNoisyAudioEvent() {
|
|
297
252
|
if (noisyAudioReceiver == null) {
|
|
298
|
-
|
|
253
|
+
RTKLogger.d("InCallManager", "startNoisyAudioEvent()");
|
|
299
254
|
IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
|
300
255
|
noisyAudioReceiver = new BroadcastReceiver() {
|
|
301
256
|
@Override
|
|
@@ -310,87 +265,22 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
310
265
|
if (reactContext != null) {
|
|
311
266
|
this.registerReceiver(noisyAudioReceiver, filter);
|
|
312
267
|
} else {
|
|
313
|
-
|
|
268
|
+
RTKLogger.w("InCallManager", "startNoisyAudioEvent() reactContext is null");
|
|
314
269
|
}
|
|
315
270
|
}
|
|
316
271
|
}
|
|
317
272
|
|
|
318
273
|
private void stopNoisyAudioEvent() {
|
|
319
274
|
if (noisyAudioReceiver != null) {
|
|
320
|
-
|
|
275
|
+
RTKLogger.d("InCallManager", "stopNoisyAudioEvent()");
|
|
321
276
|
this.unregisterReceiver(this.noisyAudioReceiver);
|
|
322
277
|
noisyAudioReceiver = null;
|
|
323
278
|
}
|
|
324
279
|
}
|
|
325
280
|
|
|
326
|
-
private void startMediaButtonEvent() {
|
|
327
|
-
if (mediaButtonReceiver == null) {
|
|
328
|
-
// Log.d(TAG, "startMediaButtonEvent()");
|
|
329
|
-
IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
|
|
330
|
-
mediaButtonReceiver = new BroadcastReceiver() {
|
|
331
|
-
@Override
|
|
332
|
-
public void onReceive(Context context, Intent intent) {
|
|
333
|
-
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
|
|
334
|
-
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
|
335
|
-
int keyCode = event.getKeyCode();
|
|
336
|
-
String keyText = "";
|
|
337
|
-
switch (keyCode) {
|
|
338
|
-
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
|
339
|
-
keyText = "KEYCODE_MEDIA_PLAY";
|
|
340
|
-
break;
|
|
341
|
-
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
|
342
|
-
keyText = "KEYCODE_MEDIA_PAUSE";
|
|
343
|
-
break;
|
|
344
|
-
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
|
345
|
-
keyText = "KEYCODE_MEDIA_PLAY_PAUSE";
|
|
346
|
-
break;
|
|
347
|
-
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
|
348
|
-
keyText = "KEYCODE_MEDIA_NEXT";
|
|
349
|
-
break;
|
|
350
|
-
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
|
351
|
-
keyText = "KEYCODE_MEDIA_PREVIOUS";
|
|
352
|
-
break;
|
|
353
|
-
case KeyEvent.KEYCODE_MEDIA_CLOSE:
|
|
354
|
-
keyText = "KEYCODE_MEDIA_CLOSE";
|
|
355
|
-
break;
|
|
356
|
-
case KeyEvent.KEYCODE_MEDIA_EJECT:
|
|
357
|
-
keyText = "KEYCODE_MEDIA_EJECT";
|
|
358
|
-
break;
|
|
359
|
-
case KeyEvent.KEYCODE_MEDIA_RECORD:
|
|
360
|
-
keyText = "KEYCODE_MEDIA_RECORD";
|
|
361
|
-
break;
|
|
362
|
-
case KeyEvent.KEYCODE_MEDIA_STOP:
|
|
363
|
-
keyText = "KEYCODE_MEDIA_STOP";
|
|
364
|
-
break;
|
|
365
|
-
default:
|
|
366
|
-
keyText = "KEYCODE_UNKNOW";
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
WritableMap data = Arguments.createMap();
|
|
370
|
-
data.putString("eventText", keyText);
|
|
371
|
-
data.putInt("eventCode", keyCode);
|
|
372
|
-
sendEvent("MediaButton", data);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
ReactContext reactContext = getReactApplicationContext();
|
|
377
|
-
if (reactContext != null) {
|
|
378
|
-
this.registerReceiver(mediaButtonReceiver, filter);
|
|
379
|
-
} else {
|
|
380
|
-
// Log.d(TAG, "startMediaButtonEvent() reactContext is null");
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
private void stopMediaButtonEvent() {
|
|
386
|
-
if (mediaButtonReceiver != null) {
|
|
387
|
-
// Log.d(TAG, "stopMediaButtonEvent()");
|
|
388
|
-
this.unregisterReceiver(this.mediaButtonReceiver);
|
|
389
|
-
mediaButtonReceiver = null;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
281
|
public void onProximitySensorChangedState(boolean isNear) {
|
|
282
|
+
RTKLogger.d("InCallManager", "onProximitySensorChangedState() isNear=" + isNear + ", selectedAudioDevice=" + getSelectedAudioDevice());
|
|
283
|
+
|
|
394
284
|
if (automatic && getSelectedAudioDevice() == AudioDevice.EARPIECE) {
|
|
395
285
|
if (isNear) {
|
|
396
286
|
turnScreenOff();
|
|
@@ -407,33 +297,33 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
407
297
|
@ReactMethod
|
|
408
298
|
public void startProximitySensor() {
|
|
409
299
|
if (!proximityManager.isProximitySupported()) {
|
|
410
|
-
|
|
300
|
+
RTKLogger.d("InCallManager", "Proximity Sensor is not supported.");
|
|
411
301
|
return;
|
|
412
302
|
}
|
|
413
303
|
if (isProximityRegistered) {
|
|
414
|
-
|
|
304
|
+
RTKLogger.d("InCallManager", "Proximity Sensor is already registered.");
|
|
415
305
|
return;
|
|
416
306
|
}
|
|
417
307
|
// --- SENSOR_DELAY_FASTEST(0 milisecs), SENSOR_DELAY_GAME(20 milisecs), SENSOR_DELAY_UI(60 milisecs), SENSOR_DELAY_NORMAL(200 milisecs)
|
|
418
308
|
if (!proximityManager.start()) {
|
|
419
|
-
|
|
309
|
+
RTKLogger.d("InCallManager", "proximityManager.start() failed. return false");
|
|
420
310
|
return;
|
|
421
311
|
}
|
|
422
|
-
|
|
312
|
+
RTKLogger.d("InCallManager", "startProximitySensor()");
|
|
423
313
|
isProximityRegistered = true;
|
|
424
314
|
}
|
|
425
315
|
|
|
426
316
|
@ReactMethod
|
|
427
317
|
public void stopProximitySensor() {
|
|
428
318
|
if (!proximityManager.isProximitySupported()) {
|
|
429
|
-
|
|
319
|
+
RTKLogger.d("InCallManager", "Proximity Sensor is not supported.");
|
|
430
320
|
return;
|
|
431
321
|
}
|
|
432
322
|
if (!isProximityRegistered) {
|
|
433
|
-
|
|
323
|
+
RTKLogger.d("InCallManager", "Proximity Sensor is not registered.");
|
|
434
324
|
return;
|
|
435
325
|
}
|
|
436
|
-
|
|
326
|
+
RTKLogger.d("InCallManager", "stopProximitySensor()");
|
|
437
327
|
proximityManager.stop();
|
|
438
328
|
isProximityRegistered = false;
|
|
439
329
|
}
|
|
@@ -472,7 +362,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
472
362
|
break;
|
|
473
363
|
}
|
|
474
364
|
|
|
475
|
-
|
|
365
|
+
RTKLogger.d("InCallManager", "onAudioFocusChange(): " + focusChange + " - " + focusChangeStr);
|
|
476
366
|
|
|
477
367
|
WritableMap data = Arguments.createMap();
|
|
478
368
|
data.putString("eventText", focusChangeStr);
|
|
@@ -484,36 +374,29 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
484
374
|
try {
|
|
485
375
|
ReactContext reactContext = getReactApplicationContext();
|
|
486
376
|
if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
|
|
487
|
-
|
|
377
|
+
RTKLogger.d("InCallManager", "Sending event: " + eventName);
|
|
488
378
|
reactContext
|
|
489
379
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
490
380
|
.emit(eventName, params);
|
|
491
381
|
} else {
|
|
492
|
-
|
|
382
|
+
RTKLogger.e("InCallManager", "sendEvent(): reactContext is null or not having CatalystInstance yet.");
|
|
493
383
|
}
|
|
494
384
|
} catch (RuntimeException e) {
|
|
495
|
-
|
|
385
|
+
RTKLogger.e("InCallManager", "sendEvent(): java.lang.RuntimeException: Trying to invoke JS before CatalystInstance has been set!");
|
|
496
386
|
}
|
|
497
387
|
}
|
|
498
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Starts the audio manager. Default mode is video call (speaker on).
|
|
391
|
+
* Use setVoiceCallMode() to switch to voice call mode (earpiece with proximity sensor).
|
|
392
|
+
*/
|
|
499
393
|
@ReactMethod
|
|
500
|
-
public void start(
|
|
501
|
-
media = _media;
|
|
502
|
-
if (media.equals("video")) {
|
|
503
|
-
defaultSpeakerOn = true;
|
|
504
|
-
} else {
|
|
505
|
-
defaultSpeakerOn = false;
|
|
506
|
-
}
|
|
507
|
-
automatic = auto;
|
|
394
|
+
public void start() {
|
|
508
395
|
if (!audioManagerActivated) {
|
|
509
396
|
audioManagerActivated = true;
|
|
510
397
|
|
|
511
|
-
|
|
398
|
+
RTKLogger.d("InCallManager", "start audioRouteManager");
|
|
512
399
|
wakeLockUtils.acquirePartialWakeLock();
|
|
513
|
-
if (mRingtone != null && mRingtone.isPlaying()) {
|
|
514
|
-
// Log.d(TAG, "stop ringtone");
|
|
515
|
-
stopRingtone(); // --- use brandnew instance
|
|
516
|
-
}
|
|
517
400
|
storeOriginalAudioSetup();
|
|
518
401
|
requestAudioFocus();
|
|
519
402
|
startEvents();
|
|
@@ -530,47 +413,93 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
530
413
|
selectedAudioDevice = AudioDevice.NONE;
|
|
531
414
|
audioDevices.clear();
|
|
532
415
|
updateAudioRoute();
|
|
533
|
-
|
|
534
|
-
if (!ringbackUriType.isEmpty()) {
|
|
535
|
-
startRingback(ringbackUriType);
|
|
536
|
-
}
|
|
537
416
|
}
|
|
538
417
|
}
|
|
539
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Stops the audio manager and releases all resources.
|
|
421
|
+
*/
|
|
422
|
+
@ReactMethod
|
|
540
423
|
public void stop() {
|
|
541
|
-
|
|
424
|
+
if (audioManagerActivated) {
|
|
425
|
+
RTKLogger.d("InCallManager", "stop() InCallManager");
|
|
426
|
+
stopEvents();
|
|
427
|
+
setSpeakerphoneOn(false);
|
|
428
|
+
setMicrophoneMute(false);
|
|
429
|
+
forceSpeakerOn = 0;
|
|
430
|
+
UiThreadUtil.runOnUiThread(() -> {
|
|
431
|
+
bluetoothManager.stop();
|
|
432
|
+
});
|
|
433
|
+
restoreOriginalAudioSetup();
|
|
434
|
+
abandonAudioFocus();
|
|
435
|
+
audioManagerActivated = false;
|
|
436
|
+
wakeLockUtils.releasePartialWakeLock();
|
|
437
|
+
}
|
|
542
438
|
}
|
|
543
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Switches to voice call mode (earpiece with proximity sensor enabled).
|
|
442
|
+
* Call this after start() to enable proximity sensor for voice calls.
|
|
443
|
+
*/
|
|
544
444
|
@ReactMethod
|
|
545
|
-
public void
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
abandonAudioFocus();
|
|
564
|
-
audioManagerActivated = false;
|
|
445
|
+
public void setVoiceCallMode() {
|
|
446
|
+
defaultSpeakerOn = false;
|
|
447
|
+
defaultAudioDevice = hasEarpiece() ? AudioDevice.EARPIECE : AudioDevice.SPEAKER_PHONE;
|
|
448
|
+
userSelectedAudioDevice = AudioDevice.NONE;
|
|
449
|
+
updateAudioRoute();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get all available audio output devices.
|
|
454
|
+
* Returns array of device names: ["SPEAKER_PHONE", "BLUETOOTH", "WIRED_HEADSET", "EARPIECE"]
|
|
455
|
+
*/
|
|
456
|
+
@ReactMethod
|
|
457
|
+
public void getAudioDevices(Promise promise) {
|
|
458
|
+
try {
|
|
459
|
+
WritableMap result = Arguments.createMap();
|
|
460
|
+
String devicesJson = "[";
|
|
461
|
+
for (AudioDevice device : audioDevices) {
|
|
462
|
+
devicesJson += "\"" + device.name() + "\",";
|
|
565
463
|
}
|
|
566
|
-
|
|
464
|
+
// Strip the last comma
|
|
465
|
+
if (devicesJson.length() > 1) {
|
|
466
|
+
devicesJson = devicesJson.substring(0, devicesJson.length() - 1);
|
|
467
|
+
}
|
|
468
|
+
devicesJson += "]";
|
|
469
|
+
|
|
470
|
+
result.putString("availableDevices", devicesJson);
|
|
471
|
+
promise.resolve(result);
|
|
472
|
+
} catch (Exception e) {
|
|
473
|
+
promise.reject("ERROR", "Failed to get audio devices", e);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Get the currently active audio output device.
|
|
479
|
+
* Returns device name: "SPEAKER_PHONE", "BLUETOOTH", "WIRED_HEADSET", "EARPIECE", or "NONE"
|
|
480
|
+
*/
|
|
481
|
+
@ReactMethod
|
|
482
|
+
public void getCurrentAudioDevice(Promise promise) {
|
|
483
|
+
try {
|
|
484
|
+
WritableMap result = Arguments.createMap();
|
|
485
|
+
result.putString("currentDevice", selectedAudioDevice != null ? selectedAudioDevice.name() : "NONE");
|
|
486
|
+
|
|
487
|
+
// Also include Bluetooth SCO state for better debugging
|
|
488
|
+
if (bluetoothManager != null) {
|
|
489
|
+
result.putString("bluetoothState", bluetoothManager.getState().toString());
|
|
490
|
+
result.putBoolean("bluetoothScoConnected",
|
|
491
|
+
bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
promise.resolve(result);
|
|
495
|
+
} catch (Exception e) {
|
|
496
|
+
promise.reject("ERROR", "Failed to get current audio device", e);
|
|
567
497
|
}
|
|
568
498
|
}
|
|
569
499
|
|
|
570
500
|
private void startEvents() {
|
|
571
501
|
startWiredHeadsetEvent();
|
|
572
502
|
startNoisyAudioEvent();
|
|
573
|
-
startMediaButtonEvent();
|
|
574
503
|
startProximitySensor(); // --- proximity event always enable, but only turn screen off when audio is routing to earpiece.
|
|
575
504
|
setKeepScreenOn(true);
|
|
576
505
|
}
|
|
@@ -578,7 +507,6 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
578
507
|
private void stopEvents() {
|
|
579
508
|
stopWiredHeadsetEvent();
|
|
580
509
|
stopNoisyAudioEvent();
|
|
581
|
-
stopMediaButtonEvent();
|
|
582
510
|
stopProximitySensor();
|
|
583
511
|
setKeepScreenOn(false);
|
|
584
512
|
turnScreenOn();
|
|
@@ -593,7 +521,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
593
521
|
String requestAudioFocusResStr = (android.os.Build.VERSION.SDK_INT >= 26)
|
|
594
522
|
? requestAudioFocusV26()
|
|
595
523
|
: requestAudioFocusOld();
|
|
596
|
-
|
|
524
|
+
RTKLogger.d("InCallManager", "requestAudioFocus(): res = " + requestAudioFocusResStr);
|
|
597
525
|
return requestAudioFocusResStr;
|
|
598
526
|
}
|
|
599
527
|
|
|
@@ -673,7 +601,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
673
601
|
String abandonAudioFocusResStr = (android.os.Build.VERSION.SDK_INT >= 26)
|
|
674
602
|
? abandonAudioFocusV26()
|
|
675
603
|
: abandonAudioFocusOld();
|
|
676
|
-
|
|
604
|
+
RTKLogger.d("InCallManager", "abandonAudioFocus(): res = " + abandonAudioFocusResStr);
|
|
677
605
|
return abandonAudioFocusResStr;
|
|
678
606
|
}
|
|
679
607
|
|
|
@@ -726,7 +654,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
726
654
|
|
|
727
655
|
@ReactMethod
|
|
728
656
|
public void pokeScreen(int timeout) {
|
|
729
|
-
|
|
657
|
+
RTKLogger.d("InCallManager", "pokeScreen()");
|
|
730
658
|
wakeLockUtils.acquirePokeFullWakeLockReleaseAfter(timeout); // --- default 3000 ms
|
|
731
659
|
}
|
|
732
660
|
|
|
@@ -766,16 +694,16 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
766
694
|
} else {
|
|
767
695
|
isInteractive = String.format("%s", mPowerManager.isScreenOn());
|
|
768
696
|
}
|
|
769
|
-
|
|
697
|
+
RTKLogger.d("InCallManager", String.format("debugScreenPowerState(): screenState='%s', isInteractive='%s', isPowerSaveMode='%s', isDeviceIdleMode='%s', isIgnoringBatteryOptimizations='%s'", screenState, isInteractive, isPowerSaveMode, isDeviceIdleMode, isIgnoringBatteryOptimizations));
|
|
770
698
|
}
|
|
771
699
|
|
|
772
700
|
@ReactMethod
|
|
773
701
|
public void turnScreenOn() {
|
|
774
702
|
if (proximityManager.isProximityWakeLockSupported()) {
|
|
775
|
-
|
|
703
|
+
RTKLogger.d("InCallManager", "turnScreenOn(): use proximity lock.");
|
|
776
704
|
proximityManager.releaseProximityWakeLock(true);
|
|
777
705
|
} else {
|
|
778
|
-
|
|
706
|
+
RTKLogger.d("InCallManager", "turnScreenOn(): proximity lock is not supported. try manually.");
|
|
779
707
|
manualTurnScreenOn();
|
|
780
708
|
}
|
|
781
709
|
}
|
|
@@ -783,22 +711,22 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
783
711
|
@ReactMethod
|
|
784
712
|
public void turnScreenOff() {
|
|
785
713
|
if (proximityManager.isProximityWakeLockSupported()) {
|
|
786
|
-
|
|
714
|
+
RTKLogger.d("InCallManager", "turnScreenOff(): use proximity lock.");
|
|
787
715
|
proximityManager.acquireProximityWakeLock();
|
|
788
716
|
} else {
|
|
789
|
-
|
|
717
|
+
RTKLogger.d("InCallManager", "turnScreenOff(): proximity lock is not supported. try manually.");
|
|
790
718
|
manualTurnScreenOff();
|
|
791
719
|
}
|
|
792
720
|
}
|
|
793
721
|
|
|
794
722
|
@ReactMethod
|
|
795
723
|
public void setKeepScreenOn(final boolean enable) {
|
|
796
|
-
|
|
724
|
+
RTKLogger.d("InCallManager", "setKeepScreenOn() " + enable);
|
|
797
725
|
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
798
726
|
public void run() {
|
|
799
727
|
Activity mCurrentActivity = getCurrentActivity();
|
|
800
728
|
if (mCurrentActivity == null) {
|
|
801
|
-
|
|
729
|
+
RTKLogger.d("InCallManager", "ReactContext doesn't have any Activity attached.");
|
|
802
730
|
return;
|
|
803
731
|
}
|
|
804
732
|
Window window = mCurrentActivity.getWindow();
|
|
@@ -814,7 +742,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
814
742
|
@ReactMethod
|
|
815
743
|
public void setSpeakerphoneOn(final boolean enable) {
|
|
816
744
|
if (enable != audioManager.isSpeakerphoneOn()) {
|
|
817
|
-
|
|
745
|
+
RTKLogger.d("InCallManager", "setSpeakerphoneOn(): " + enable);
|
|
818
746
|
audioManager.setSpeakerphoneOn(enable);
|
|
819
747
|
}
|
|
820
748
|
}
|
|
@@ -831,7 +759,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
831
759
|
if (flag < -1 || flag > 1) {
|
|
832
760
|
return;
|
|
833
761
|
}
|
|
834
|
-
|
|
762
|
+
RTKLogger.d("InCallManager", "setForceSpeakerphoneOn() flag: " + flag);
|
|
835
763
|
forceSpeakerOn = flag;
|
|
836
764
|
|
|
837
765
|
// --- will call updateAudioDeviceState()
|
|
@@ -850,622 +778,14 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
850
778
|
@ReactMethod
|
|
851
779
|
public void setMicrophoneMute(final boolean enable) {
|
|
852
780
|
if (enable != audioManager.isMicrophoneMute()) {
|
|
853
|
-
|
|
781
|
+
RTKLogger.d("InCallManager", "setMicrophoneMute(): " + enable);
|
|
854
782
|
audioManager.setMicrophoneMute(enable);
|
|
855
783
|
}
|
|
856
784
|
}
|
|
857
785
|
|
|
858
|
-
/**
|
|
859
|
-
* This is part of start() process.
|
|
860
|
-
* ringbackUriType must not empty. empty means do not play.
|
|
861
|
-
*/
|
|
862
|
-
@ReactMethod
|
|
863
|
-
public void startRingback(final String ringbackUriType) {
|
|
864
|
-
if (ringbackUriType.isEmpty()) {
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
try {
|
|
868
|
-
// Log.d(TAG, "startRingback(): UriType=" + ringbackUriType);
|
|
869
|
-
|
|
870
|
-
if (mRingback != null) {
|
|
871
|
-
if (mRingback.isPlaying()) {
|
|
872
|
-
// Log.d(TAG, "startRingback(): is already playing");
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
stopRingback(); // --- use brandnew instance
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
Uri ringbackUri;
|
|
880
|
-
Map data = new HashMap<String, Object>();
|
|
881
|
-
data.put("name", "mRingback");
|
|
882
|
-
|
|
883
|
-
// --- use ToneGenerator instead file uri
|
|
884
|
-
if (ringbackUriType.equals("_DTMF_")) {
|
|
885
|
-
mRingback = new myToneGenerator(myToneGenerator.RINGBACK);
|
|
886
|
-
mRingback.startPlay(data);
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
ringbackUri = getRingbackUri(ringbackUriType);
|
|
891
|
-
if (ringbackUri == null) {
|
|
892
|
-
// Log.d(TAG, "startRingback(): no available media");
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
mRingback = new myMediaPlayer();
|
|
897
|
-
data.put("sourceUri", ringbackUri);
|
|
898
|
-
data.put("setLooping", true);
|
|
899
|
-
|
|
900
|
-
//data.put("audioStream", AudioManager.STREAM_VOICE_CALL); // --- lagacy
|
|
901
|
-
// --- The ringback doesn't have to be a DTMF.
|
|
902
|
-
// --- Should use VOICE_COMMUNICATION for sound during call or it may be silenced.
|
|
903
|
-
data.put("audioUsage", AudioAttributes.USAGE_VOICE_COMMUNICATION);
|
|
904
|
-
data.put("audioContentType", AudioAttributes.CONTENT_TYPE_MUSIC);
|
|
905
|
-
|
|
906
|
-
setMediaPlayerEvents((MediaPlayer)mRingback, "mRingback");
|
|
907
|
-
|
|
908
|
-
mRingback.startPlay(data);
|
|
909
|
-
} catch(Exception e) {
|
|
910
|
-
// Log.d(TAG, "startRingback() failed", e);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
@ReactMethod
|
|
915
|
-
public void stopRingback() {
|
|
916
|
-
try {
|
|
917
|
-
if (mRingback != null) {
|
|
918
|
-
mRingback.stopPlay();
|
|
919
|
-
mRingback = null;
|
|
920
|
-
}
|
|
921
|
-
} catch(Exception e) {
|
|
922
|
-
// Log.d(TAG, "stopRingback() failed");
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* This is part of start() process.
|
|
928
|
-
* busytoneUriType must not empty. empty means do not play.
|
|
929
|
-
* return false to indicate play tone failed and should be stop() immediately
|
|
930
|
-
* otherwise, it will stop() after a tone completed.
|
|
931
|
-
*/
|
|
932
|
-
public boolean startBusytone(final String busytoneUriType) {
|
|
933
|
-
if (busytoneUriType.isEmpty()) {
|
|
934
|
-
return false;
|
|
935
|
-
}
|
|
936
|
-
try {
|
|
937
|
-
// Log.d(TAG, "startBusytone(): UriType=" + busytoneUriType);
|
|
938
|
-
if (mBusytone != null) {
|
|
939
|
-
if (mBusytone.isPlaying()) {
|
|
940
|
-
// Log.d(TAG, "startBusytone(): is already playing");
|
|
941
|
-
return false;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
stopBusytone(); // --- use brandnew instance
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
Uri busytoneUri;
|
|
948
|
-
Map data = new HashMap<String, Object>();
|
|
949
|
-
data.put("name", "mBusytone");
|
|
950
|
-
|
|
951
|
-
// --- use ToneGenerator instead file uri
|
|
952
|
-
if (busytoneUriType.equals("_DTMF_")) {
|
|
953
|
-
mBusytone = new myToneGenerator(myToneGenerator.BUSY);
|
|
954
|
-
mBusytone.startPlay(data);
|
|
955
|
-
return true;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
busytoneUri = getBusytoneUri(busytoneUriType);
|
|
959
|
-
if (busytoneUri == null) {
|
|
960
|
-
// Log.d(TAG, "startBusytone(): no available media");
|
|
961
|
-
return false;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
mBusytone = new myMediaPlayer();
|
|
965
|
-
|
|
966
|
-
data.put("sourceUri", busytoneUri);
|
|
967
|
-
data.put("setLooping", false);
|
|
968
|
-
//data.put("audioStream", AudioManager.STREAM_VOICE_CALL); // --- lagacy
|
|
969
|
-
// --- Should use VOICE_COMMUNICATION for sound during a call or it may be silenced.
|
|
970
|
-
data.put("audioUsage", AudioAttributes.USAGE_VOICE_COMMUNICATION);
|
|
971
|
-
data.put("audioContentType", AudioAttributes.CONTENT_TYPE_SONIFICATION); // --- CONTENT_TYPE_MUSIC?
|
|
972
|
-
|
|
973
|
-
setMediaPlayerEvents((MediaPlayer)mBusytone, "mBusytone");
|
|
974
|
-
mBusytone.startPlay(data);
|
|
975
|
-
return true;
|
|
976
|
-
} catch(Exception e) {
|
|
977
|
-
// Log.d(TAG, "startBusytone() failed", e);
|
|
978
|
-
return false;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
public void stopBusytone() {
|
|
983
|
-
try {
|
|
984
|
-
if (mBusytone != null) {
|
|
985
|
-
mBusytone.stopPlay();
|
|
986
|
-
mBusytone = null;
|
|
987
|
-
}
|
|
988
|
-
} catch(Exception e) {
|
|
989
|
-
// Log.d(TAG, "stopBusytone() failed");
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
@ReactMethod
|
|
994
|
-
public void startRingtone(final String ringtoneUriType, final int seconds) {
|
|
995
|
-
Thread thread = new Thread() {
|
|
996
|
-
@Override
|
|
997
|
-
public void run() {
|
|
998
|
-
try {
|
|
999
|
-
Looper.prepare();
|
|
1000
|
-
|
|
1001
|
-
// Log.d(TAG, "startRingtone(): UriType=" + ringtoneUriType);
|
|
1002
|
-
if (mRingtone != null) {
|
|
1003
|
-
if (mRingtone.isPlaying()) {
|
|
1004
|
-
// Log.d(TAG, "startRingtone(): is already playing");
|
|
1005
|
-
return;
|
|
1006
|
-
} else {
|
|
1007
|
-
stopRingtone(); // --- use brandnew instance
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
//if (!audioManager.isStreamMute(AudioManager.STREAM_RING)) {
|
|
1012
|
-
//if (origRingerMode == AudioManager.RINGER_MODE_NORMAL) {
|
|
1013
|
-
if (audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) {
|
|
1014
|
-
// Log.d(TAG, "startRingtone(): ringer is silent. leave without play.");
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// --- there is no _DTMF_ option in startRingtone()
|
|
1019
|
-
Uri ringtoneUri = getRingtoneUri(ringtoneUriType);
|
|
1020
|
-
if (ringtoneUri == null) {
|
|
1021
|
-
// Log.d(TAG, "startRingtone(): no available media");
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
if (audioManagerActivated) {
|
|
1026
|
-
InCallManagerModule.this.stop();
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
wakeLockUtils.acquirePartialWakeLock();
|
|
1030
|
-
|
|
1031
|
-
storeOriginalAudioSetup();
|
|
1032
|
-
Map data = new HashMap<String, Object>();
|
|
1033
|
-
mRingtone = new myMediaPlayer();
|
|
1034
|
-
|
|
1035
|
-
data.put("name", "mRingtone");
|
|
1036
|
-
data.put("sourceUri", ringtoneUri);
|
|
1037
|
-
data.put("setLooping", true);
|
|
1038
|
-
|
|
1039
|
-
//data.put("audioStream", AudioManager.STREAM_RING); // --- lagacy
|
|
1040
|
-
data.put("audioUsage", AudioAttributes.USAGE_NOTIFICATION_RINGTONE); // --- USAGE_NOTIFICATION_COMMUNICATION_REQUEST?
|
|
1041
|
-
data.put("audioContentType", AudioAttributes.CONTENT_TYPE_MUSIC);
|
|
1042
|
-
|
|
1043
|
-
setMediaPlayerEvents((MediaPlayer) mRingtone, "mRingtone");
|
|
1044
|
-
|
|
1045
|
-
mRingtone.startPlay(data);
|
|
1046
|
-
|
|
1047
|
-
if (seconds > 0) {
|
|
1048
|
-
mRingtoneCountDownHandler = new Handler();
|
|
1049
|
-
mRingtoneCountDownHandler.postDelayed(new Runnable() {
|
|
1050
|
-
public void run() {
|
|
1051
|
-
try {
|
|
1052
|
-
// Log.d(TAG, String.format("mRingtoneCountDownHandler.stopRingtone() timeout after %d seconds", seconds));
|
|
1053
|
-
stopRingtone();
|
|
1054
|
-
} catch(Exception e) {
|
|
1055
|
-
// Log.d(TAG, "mRingtoneCountDownHandler.stopRingtone() failed.");
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
}, seconds * 1000);
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
Looper.loop();
|
|
1062
|
-
} catch(Exception e) {
|
|
1063
|
-
wakeLockUtils.releasePartialWakeLock();
|
|
1064
|
-
// Log.e(TAG, "startRingtone() failed", e);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
|
|
1069
|
-
thread.start();
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
@ReactMethod
|
|
1073
|
-
public void stopRingtone() {
|
|
1074
|
-
Thread thread = new Thread() {
|
|
1075
|
-
@Override
|
|
1076
|
-
public void run() {
|
|
1077
|
-
try {
|
|
1078
|
-
if (mRingtone != null) {
|
|
1079
|
-
mRingtone.stopPlay();
|
|
1080
|
-
mRingtone = null;
|
|
1081
|
-
restoreOriginalAudioSetup();
|
|
1082
|
-
}
|
|
1083
|
-
if (mRingtoneCountDownHandler != null) {
|
|
1084
|
-
mRingtoneCountDownHandler.removeCallbacksAndMessages(null);
|
|
1085
|
-
mRingtoneCountDownHandler = null;
|
|
1086
|
-
}
|
|
1087
|
-
} catch (Exception e) {
|
|
1088
|
-
// Log.d(TAG, "stopRingtone() failed");
|
|
1089
|
-
}
|
|
1090
|
-
wakeLockUtils.releasePartialWakeLock();
|
|
1091
|
-
}
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
thread.start();
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
private void setMediaPlayerEvents(MediaPlayer mp, final String name) {
|
|
1098
|
-
|
|
1099
|
-
mp.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
|
1100
|
-
//http://developer.android.com/reference/android/media/MediaPlayer.OnErrorListener.html
|
|
1101
|
-
@Override
|
|
1102
|
-
public boolean onError(MediaPlayer mp, int what, int extra) {
|
|
1103
|
-
// Log.d(TAG, String.format("MediaPlayer %s onError(). what: %d, extra: %d", name, what, extra));
|
|
1104
|
-
//return True if the method handled the error
|
|
1105
|
-
//return False, or not having an OnErrorListener at all, will cause the OnCompletionListener to be called. Get news & tips
|
|
1106
|
-
return true;
|
|
1107
|
-
}
|
|
1108
|
-
});
|
|
1109
|
-
|
|
1110
|
-
mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
|
|
1111
|
-
//http://developer.android.com/reference/android/media/MediaPlayer.OnInfoListener.html
|
|
1112
|
-
@Override
|
|
1113
|
-
public boolean onInfo(MediaPlayer mp, int what, int extra) {
|
|
1114
|
-
// Log.d(TAG, String.format("MediaPlayer %s onInfo(). what: %d, extra: %d", name, what, extra));
|
|
1115
|
-
//return True if the method handled the info
|
|
1116
|
-
//return False, or not having an OnInfoListener at all, will cause the info to be discarded.
|
|
1117
|
-
return true;
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
|
|
1121
|
-
mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
|
1122
|
-
@Override
|
|
1123
|
-
public void onPrepared(MediaPlayer mp) {
|
|
1124
|
-
// Log.d(TAG, String.format("MediaPlayer %s onPrepared(), start play, isSpeakerPhoneOn %b", name, audioManager.isSpeakerphoneOn()));
|
|
1125
|
-
if (name.equals("mBusytone")) {
|
|
1126
|
-
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
1127
|
-
} else if (name.equals("mRingback")) {
|
|
1128
|
-
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
1129
|
-
} else if (name.equals("mRingtone")) {
|
|
1130
|
-
audioManager.setMode(AudioManager.MODE_RINGTONE);
|
|
1131
|
-
}
|
|
1132
|
-
updateAudioRoute();
|
|
1133
|
-
mp.start();
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
|
1138
|
-
@Override
|
|
1139
|
-
public void onCompletion(MediaPlayer mp) {
|
|
1140
|
-
// Log.d(TAG, String.format("MediaPlayer %s onCompletion()", name));
|
|
1141
|
-
if (name.equals("mBusytone")) {
|
|
1142
|
-
// Log.d(TAG, "MyMediaPlayer(): invoke stop()");
|
|
1143
|
-
stop();
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
// ===== File Uri Start =====
|
|
1152
|
-
@ReactMethod
|
|
1153
|
-
public void getAudioUriJS(String audioType, String fileType, Promise promise) {
|
|
1154
|
-
Uri result = null;
|
|
1155
|
-
if (audioType.equals("ringback")) {
|
|
1156
|
-
result = getRingbackUri(fileType);
|
|
1157
|
-
} else if (audioType.equals("busytone")) {
|
|
1158
|
-
result = getBusytoneUri(fileType);
|
|
1159
|
-
} else if (audioType.equals("ringtone")) {
|
|
1160
|
-
result = getRingtoneUri(fileType);
|
|
1161
|
-
}
|
|
1162
|
-
try {
|
|
1163
|
-
if (result != null) {
|
|
1164
|
-
promise.resolve(result.toString());
|
|
1165
|
-
} else {
|
|
1166
|
-
promise.reject("failed");
|
|
1167
|
-
}
|
|
1168
|
-
} catch (Exception e) {
|
|
1169
|
-
promise.reject("failed");
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
private Uri getRingtoneUri(final String _type) {
|
|
1174
|
-
final String fileBundle = "incallmanager_ringtone";
|
|
1175
|
-
final String fileBundleExt = "mp3";
|
|
1176
|
-
final String fileSysWithExt = "media_volume.ogg";
|
|
1177
|
-
final String fileSysPath = "/system/media/audio/ui"; // --- every devices all ships with different in ringtone. maybe ui sounds are more "stock"
|
|
1178
|
-
String type;
|
|
1179
|
-
// --- _type MAY be empty
|
|
1180
|
-
if (_type.equals("_DEFAULT_") || _type.isEmpty()) {
|
|
1181
|
-
//type = fileSysWithExt;
|
|
1182
|
-
return getDefaultUserUri("defaultRingtoneUri");
|
|
1183
|
-
} else {
|
|
1184
|
-
type = _type;
|
|
1185
|
-
}
|
|
1186
|
-
return getAudioUri(type, fileBundle, fileBundleExt, fileSysWithExt, fileSysPath, "bundleRingtoneUri", "defaultRingtoneUri");
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
private Uri getRingbackUri(final String _type) {
|
|
1190
|
-
final String fileBundle = "incallmanager_ringback";
|
|
1191
|
-
final String fileBundleExt = "mp3";
|
|
1192
|
-
final String fileSysWithExt = "media_volume.ogg";
|
|
1193
|
-
final String fileSysPath = "/system/media/audio/ui"; // --- every devices all ships with different in ringtone. maybe ui sounds are more "stock"
|
|
1194
|
-
String type;
|
|
1195
|
-
// --- _type would never be empty here. just in case.
|
|
1196
|
-
if (_type.equals("_DEFAULT_") || _type.isEmpty()) {
|
|
1197
|
-
//type = fileSysWithExt;
|
|
1198
|
-
return getDefaultUserUri("defaultRingbackUri");
|
|
1199
|
-
} else {
|
|
1200
|
-
type = _type;
|
|
1201
|
-
}
|
|
1202
|
-
return getAudioUri(type, fileBundle, fileBundleExt, fileSysWithExt, fileSysPath, "bundleRingbackUri", "defaultRingbackUri");
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
private Uri getBusytoneUri(final String _type) {
|
|
1206
|
-
final String fileBundle = "incallmanager_busytone";
|
|
1207
|
-
final String fileBundleExt = "mp3";
|
|
1208
|
-
final String fileSysWithExt = "LowBattery.ogg";
|
|
1209
|
-
final String fileSysPath = "/system/media/audio/ui"; // --- every devices all ships with different in ringtone. maybe ui sounds are more "stock"
|
|
1210
|
-
String type;
|
|
1211
|
-
// --- _type would never be empty here. just in case.
|
|
1212
|
-
if (_type.equals("_DEFAULT_") || _type.isEmpty()) {
|
|
1213
|
-
//type = fileSysWithExt; // ---
|
|
1214
|
-
return getDefaultUserUri("defaultBusytoneUri");
|
|
1215
|
-
} else {
|
|
1216
|
-
type = _type;
|
|
1217
|
-
}
|
|
1218
|
-
return getAudioUri(type, fileBundle, fileBundleExt, fileSysWithExt, fileSysPath, "bundleBusytoneUri", "defaultBusytoneUri");
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
private Uri getAudioUri(final String _type, final String fileBundle, final String fileBundleExt, final String fileSysWithExt, final String fileSysPath, final String uriBundle, final String uriDefault) {
|
|
1222
|
-
String type = _type;
|
|
1223
|
-
if (type.equals("_BUNDLE_")) {
|
|
1224
|
-
if (audioUriMap.get(uriBundle) == null) {
|
|
1225
|
-
int res = 0;
|
|
1226
|
-
ReactContext reactContext = getReactApplicationContext();
|
|
1227
|
-
if (reactContext != null) {
|
|
1228
|
-
res = reactContext.getResources().getIdentifier(fileBundle, "raw", mPackageName);
|
|
1229
|
-
} else {
|
|
1230
|
-
// Log.d(TAG, "getAudioUri() reactContext is null");
|
|
1231
|
-
}
|
|
1232
|
-
if (res <= 0) {
|
|
1233
|
-
// Log.d(TAG, String.format("getAudioUri() %s.%s not found in bundle.", fileBundle, fileBundleExt));
|
|
1234
|
-
audioUriMap.put(uriBundle, null);
|
|
1235
|
-
//type = fileSysWithExt;
|
|
1236
|
-
return getDefaultUserUri(uriDefault); // --- if specified bundle but not found, use default directlly
|
|
1237
|
-
} else {
|
|
1238
|
-
audioUriMap.put(uriBundle, Uri.parse("android.resource://" + mPackageName + "/" + Integer.toString(res)));
|
|
1239
|
-
//bundleRingtoneUri = Uri.parse("android.resource://" + reactContext.getPackageName() + "/" + R.raw.incallmanager_ringtone);
|
|
1240
|
-
//bundleRingtoneUri = Uri.parse("android.resource://" + reactContext.getPackageName() + "/raw/incallmanager_ringtone");
|
|
1241
|
-
// Log.d(TAG, "getAudioUri() using: " + type);
|
|
1242
|
-
return audioUriMap.get(uriBundle);
|
|
1243
|
-
}
|
|
1244
|
-
} else {
|
|
1245
|
-
// Log.d(TAG, "getAudioUri() using: " + type);
|
|
1246
|
-
return audioUriMap.get(uriBundle);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// --- Check file every time in case user deleted.
|
|
1251
|
-
final String target = fileSysPath + "/" + type;
|
|
1252
|
-
Uri _uri = getSysFileUri(target);
|
|
1253
|
-
if (_uri == null) {
|
|
1254
|
-
// Log.d(TAG, "getAudioUri() using user default");
|
|
1255
|
-
return getDefaultUserUri(uriDefault);
|
|
1256
|
-
} else {
|
|
1257
|
-
// Log.d(TAG, "getAudioUri() using internal: " + target);
|
|
1258
|
-
audioUriMap.put(uriDefault, _uri);
|
|
1259
|
-
return _uri;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
private Uri getSysFileUri(final String target) {
|
|
1264
|
-
File file = new File(target);
|
|
1265
|
-
if (file.isFile()) {
|
|
1266
|
-
return Uri.fromFile(file);
|
|
1267
|
-
}
|
|
1268
|
-
return null;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
private Uri getDefaultUserUri(final String type) {
|
|
1272
|
-
// except ringtone, it doesn't suppose to be go here. and every android has different files unlike apple;
|
|
1273
|
-
if (type.equals("defaultRingtoneUri")) {
|
|
1274
|
-
return Settings.System.DEFAULT_RINGTONE_URI;
|
|
1275
|
-
} else if (type.equals("defaultRingbackUri")) {
|
|
1276
|
-
return Settings.System.DEFAULT_RINGTONE_URI;
|
|
1277
|
-
} else if (type.equals("defaultBusytoneUri")) {
|
|
1278
|
-
return Settings.System.DEFAULT_NOTIFICATION_URI; // --- DEFAULT_ALARM_ALERT_URI
|
|
1279
|
-
} else {
|
|
1280
|
-
return Settings.System.DEFAULT_NOTIFICATION_URI;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
// ===== File Uri End =====
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
// ===== Internal Classes Start =====
|
|
1287
|
-
private class myToneGenerator extends Thread implements MyPlayerInterface {
|
|
1288
|
-
private int toneType;
|
|
1289
|
-
private int toneCategory;
|
|
1290
|
-
private boolean playing = false;
|
|
1291
|
-
private static final int maxWaitTimeMs = 3600000; // 1 hour fairly enough
|
|
1292
|
-
private static final int loadBufferWaitTimeMs = 20;
|
|
1293
|
-
private static final int toneVolume = 100; // The volume of the tone, given in percentage of maximum volume (from 0-100).
|
|
1294
|
-
// --- constant in ToneGenerator all below 100
|
|
1295
|
-
public static final int BEEP = 101;
|
|
1296
|
-
public static final int BUSY = 102;
|
|
1297
|
-
public static final int CALLEND = 103;
|
|
1298
|
-
public static final int CALLWAITING = 104;
|
|
1299
|
-
public static final int RINGBACK = 105;
|
|
1300
|
-
public static final int SILENT = 106;
|
|
1301
|
-
public int customWaitTimeMs = maxWaitTimeMs;
|
|
1302
|
-
public String caller;
|
|
1303
|
-
|
|
1304
|
-
myToneGenerator(final int t) {
|
|
1305
|
-
super();
|
|
1306
|
-
toneCategory = t;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
public void setCustomWaitTime(final int ms) {
|
|
1310
|
-
customWaitTimeMs = ms;
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
@Override
|
|
1314
|
-
public void startPlay(final Map data) {
|
|
1315
|
-
String name = (String) data.get("name");
|
|
1316
|
-
caller = name;
|
|
1317
|
-
start();
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
@Override
|
|
1321
|
-
public boolean isPlaying() {
|
|
1322
|
-
return playing;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
@Override
|
|
1326
|
-
public void stopPlay() {
|
|
1327
|
-
synchronized (this) {
|
|
1328
|
-
if (playing) {
|
|
1329
|
-
notify();
|
|
1330
|
-
}
|
|
1331
|
-
playing = false;
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
@Override
|
|
1336
|
-
public void run() {
|
|
1337
|
-
int toneWaitTimeMs;
|
|
1338
|
-
switch (toneCategory) {
|
|
1339
|
-
case SILENT:
|
|
1340
|
-
//toneType = ToneGenerator.TONE_CDMA_SIGNAL_OFF;
|
|
1341
|
-
toneType = ToneGenerator.TONE_CDMA_ANSWER;
|
|
1342
|
-
toneWaitTimeMs = 1000;
|
|
1343
|
-
break;
|
|
1344
|
-
case BUSY:
|
|
1345
|
-
//toneType = ToneGenerator.TONE_SUP_BUSY;
|
|
1346
|
-
//toneType = ToneGenerator.TONE_SUP_CONGESTION;
|
|
1347
|
-
//toneType = ToneGenerator.TONE_SUP_CONGESTION_ABBREV;
|
|
1348
|
-
//toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY;
|
|
1349
|
-
//toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT;
|
|
1350
|
-
toneType = ToneGenerator.TONE_SUP_RADIO_NOTAVAIL;
|
|
1351
|
-
toneWaitTimeMs = 4000;
|
|
1352
|
-
break;
|
|
1353
|
-
case RINGBACK:
|
|
1354
|
-
//toneType = ToneGenerator.TONE_SUP_RINGTONE;
|
|
1355
|
-
toneType = ToneGenerator.TONE_CDMA_NETWORK_USA_RINGBACK;
|
|
1356
|
-
toneWaitTimeMs = maxWaitTimeMs; // [STOP MANUALLY]
|
|
1357
|
-
break;
|
|
1358
|
-
case CALLEND:
|
|
1359
|
-
toneType = ToneGenerator.TONE_PROP_PROMPT;
|
|
1360
|
-
toneWaitTimeMs = 200; // plays when call ended
|
|
1361
|
-
break;
|
|
1362
|
-
case CALLWAITING:
|
|
1363
|
-
//toneType = ToneGenerator.TONE_CDMA_NETWORK_CALLWAITING;
|
|
1364
|
-
toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
|
|
1365
|
-
toneWaitTimeMs = maxWaitTimeMs; // [STOP MANUALLY]
|
|
1366
|
-
break;
|
|
1367
|
-
case BEEP:
|
|
1368
|
-
//toneType = ToneGenerator.TONE_SUP_PIP;
|
|
1369
|
-
//toneType = ToneGenerator.TONE_CDMA_PIP;
|
|
1370
|
-
//toneType = ToneGenerator.TONE_SUP_RADIO_ACK;
|
|
1371
|
-
//toneType = ToneGenerator.TONE_PROP_BEEP;
|
|
1372
|
-
toneType = ToneGenerator.TONE_PROP_BEEP2;
|
|
1373
|
-
toneWaitTimeMs = 1000; // plays when call ended
|
|
1374
|
-
break;
|
|
1375
|
-
default:
|
|
1376
|
-
// --- use ToneGenerator internal type.
|
|
1377
|
-
// Log.d(TAG, "myToneGenerator: use internal tone type: " + toneCategory);
|
|
1378
|
-
toneType = toneCategory;
|
|
1379
|
-
toneWaitTimeMs = customWaitTimeMs;
|
|
1380
|
-
}
|
|
1381
|
-
// Log.d(TAG, String.format("myToneGenerator: toneCategory: %d ,toneType: %d, toneWaitTimeMs: %d", toneCategory, toneType, toneWaitTimeMs));
|
|
1382
|
-
|
|
1383
|
-
ToneGenerator tg;
|
|
1384
|
-
try {
|
|
1385
|
-
tg = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, toneVolume);
|
|
1386
|
-
} catch (RuntimeException e) {
|
|
1387
|
-
// Log.d(TAG, "myToneGenerator: Exception caught while creating ToneGenerator: " + e);
|
|
1388
|
-
tg = null;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
if (tg != null) {
|
|
1392
|
-
synchronized (this) {
|
|
1393
|
-
if (!playing) {
|
|
1394
|
-
playing = true;
|
|
1395
|
-
|
|
1396
|
-
// --- make sure audio routing, or it will be wired when switch suddenly
|
|
1397
|
-
if (caller.equals("mBusytone")) {
|
|
1398
|
-
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
1399
|
-
} else if (caller.equals("mRingback")) {
|
|
1400
|
-
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
1401
|
-
} else if (caller.equals("mRingtone")) {
|
|
1402
|
-
audioManager.setMode(AudioManager.MODE_RINGTONE);
|
|
1403
|
-
}
|
|
1404
|
-
InCallManagerModule.this.updateAudioRoute();
|
|
1405
|
-
|
|
1406
|
-
tg.startTone(toneType);
|
|
1407
|
-
try {
|
|
1408
|
-
wait(toneWaitTimeMs + loadBufferWaitTimeMs);
|
|
1409
|
-
} catch (InterruptedException e) {
|
|
1410
|
-
// Log.d(TAG, "myToneGenerator stopped. toneType: " + toneType);
|
|
1411
|
-
}
|
|
1412
|
-
tg.stopTone();
|
|
1413
|
-
}
|
|
1414
|
-
playing = false;
|
|
1415
|
-
tg.release();
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
// Log.d(TAG, "MyToneGenerator(): play finished. caller=" + caller);
|
|
1419
|
-
if (caller.equals("mBusytone")) {
|
|
1420
|
-
// Log.d(TAG, "MyToneGenerator(): invoke stop()");
|
|
1421
|
-
InCallManagerModule.this.stop();
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
private class myMediaPlayer extends MediaPlayer implements MyPlayerInterface {
|
|
1427
|
-
|
|
1428
|
-
@Override
|
|
1429
|
-
public void stopPlay() {
|
|
1430
|
-
stop();
|
|
1431
|
-
reset();
|
|
1432
|
-
release();
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
@Override
|
|
1436
|
-
public void startPlay(final Map data) {
|
|
1437
|
-
try {
|
|
1438
|
-
ReactContext reactContext = getReactApplicationContext();
|
|
1439
|
-
|
|
1440
|
-
setDataSource(reactContext, (Uri) data.get("sourceUri"));
|
|
1441
|
-
setLooping((Boolean) data.get("setLooping"));
|
|
1442
|
-
|
|
1443
|
-
// --- the `minSdkVersion` is 21 since RN 64,
|
|
1444
|
-
// --- if you want to suuport api < 21, comment out `setAudioAttributes` and use `setAudioStreamType((Integer) data.get("audioStream"))` instead
|
|
1445
|
-
setAudioAttributes(
|
|
1446
|
-
new AudioAttributes.Builder()
|
|
1447
|
-
.setUsage((Integer) data.get("audioUsage"))
|
|
1448
|
-
.setContentType((Integer) data.get("audioContentType"))
|
|
1449
|
-
.build()
|
|
1450
|
-
);
|
|
1451
|
-
|
|
1452
|
-
// -- will start at onPrepared() event
|
|
1453
|
-
prepareAsync();
|
|
1454
|
-
} catch (Exception e) {
|
|
1455
|
-
// Log.d(TAG, "startPlay() failed", e);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
@Override
|
|
1460
|
-
public boolean isPlaying() {
|
|
1461
|
-
return super.isPlaying();
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
// ===== Internal Classes End =====
|
|
1465
|
-
|
|
1466
786
|
@ReactMethod
|
|
1467
787
|
public void chooseAudioRoute(String audioRoute, Promise promise) {
|
|
1468
|
-
|
|
788
|
+
RTKLogger.d("InCallManager", "chooseAudioRoute(): user choose audioDevice = " + audioRoute);
|
|
1469
789
|
|
|
1470
790
|
if (audioRoute.equals(AudioDevice.EARPIECE.name()) || audioRoute.equals("earpiece")) {
|
|
1471
791
|
selectAudioDevice(AudioDevice.EARPIECE);
|
|
@@ -1479,46 +799,35 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1479
799
|
promise.resolve(getAudioDeviceStatusMap());
|
|
1480
800
|
}
|
|
1481
801
|
|
|
1482
|
-
private static int getRandomInteger(int min, int max) {
|
|
1483
|
-
if (min >= max) {
|
|
1484
|
-
throw new IllegalArgumentException("max must be greater than min");
|
|
1485
|
-
}
|
|
1486
|
-
Random random = new Random();
|
|
1487
|
-
return random.nextInt((max - min) + 1) + min;
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
802
|
private void pause() {
|
|
1491
803
|
if (audioManagerActivated) {
|
|
1492
|
-
|
|
804
|
+
RTKLogger.d("InCallManager", "pause audioRouteManager");
|
|
1493
805
|
stopEvents();
|
|
1494
806
|
}
|
|
1495
807
|
}
|
|
1496
808
|
|
|
1497
809
|
private void resume() {
|
|
1498
810
|
if (audioManagerActivated) {
|
|
1499
|
-
|
|
811
|
+
RTKLogger.d("InCallManager", "resume audioRouteManager");
|
|
1500
812
|
startEvents();
|
|
1501
813
|
}
|
|
1502
814
|
}
|
|
1503
815
|
|
|
1504
816
|
@Override
|
|
1505
817
|
public void onHostResume() {
|
|
1506
|
-
|
|
818
|
+
RTKLogger.d("InCallManager", "onResume()");
|
|
1507
819
|
//resume();
|
|
1508
820
|
}
|
|
1509
821
|
|
|
1510
822
|
@Override
|
|
1511
823
|
public void onHostPause() {
|
|
1512
|
-
|
|
824
|
+
RTKLogger.d("InCallManager", "onPause()");
|
|
1513
825
|
//pause();
|
|
1514
826
|
}
|
|
1515
827
|
|
|
1516
828
|
@Override
|
|
1517
829
|
public void onHostDestroy() {
|
|
1518
|
-
|
|
1519
|
-
stopRingtone();
|
|
1520
|
-
stopRingback();
|
|
1521
|
-
stopBusytone();
|
|
830
|
+
RTKLogger.d("InCallManager", "onDestroy()");
|
|
1522
831
|
stop();
|
|
1523
832
|
}
|
|
1524
833
|
|
|
@@ -1532,9 +841,9 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1532
841
|
// ===== NOTE: below functions is based on appRTC DEMO M64 ===== //
|
|
1533
842
|
/** Changes selection of the currently active audio device. */
|
|
1534
843
|
private void setAudioDeviceInternal(AudioDevice device) {
|
|
1535
|
-
|
|
844
|
+
RTKLogger.d("InCallManager", "setAudioDeviceInternal(device=" + device + ")");
|
|
1536
845
|
if (!audioDevices.contains(device)) {
|
|
1537
|
-
|
|
846
|
+
RTKLogger.e("InCallManager", "specified audio device does not exist");
|
|
1538
847
|
return;
|
|
1539
848
|
}
|
|
1540
849
|
switch (device) {
|
|
@@ -1544,10 +853,19 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1544
853
|
stopProximitySensor();
|
|
1545
854
|
break;
|
|
1546
855
|
case EARPIECE:
|
|
856
|
+
RTKLogger.d("InCallManager", "Routing to EARPIECE");
|
|
1547
857
|
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
1548
858
|
setSpeakerphoneOn(false);
|
|
1549
|
-
startProximitySensor();
|
|
1550
859
|
audioManager.setWiredHeadsetOn(false);
|
|
860
|
+
// Ensure audio is routed to earpiece by stopping any Bluetooth SCO
|
|
861
|
+
if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
|
|
862
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING) {
|
|
863
|
+
RTKLogger.d("InCallManager", "Stopping Bluetooth SCO for earpiece");
|
|
864
|
+
bluetoothManager.stopScoAudio();
|
|
865
|
+
}
|
|
866
|
+
startProximitySensor();
|
|
867
|
+
RTKLogger.d("InCallManager", "Earpiece routing complete. Mode: " + audioManager.getMode()
|
|
868
|
+
+ ", Speaker: " + audioManager.isSpeakerphoneOn());
|
|
1551
869
|
break;
|
|
1552
870
|
case WIRED_HEADSET:
|
|
1553
871
|
audioManager.setWiredHeadsetOn(true);
|
|
@@ -1560,7 +878,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1560
878
|
stopProximitySensor();
|
|
1561
879
|
break;
|
|
1562
880
|
default:
|
|
1563
|
-
|
|
881
|
+
RTKLogger.e("InCallManager", "Invalid audio device selection");
|
|
1564
882
|
break;
|
|
1565
883
|
}
|
|
1566
884
|
selectedAudioDevice = device;
|
|
@@ -1585,20 +903,20 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1585
903
|
}
|
|
1586
904
|
break;
|
|
1587
905
|
default:
|
|
1588
|
-
|
|
906
|
+
RTKLogger.e("InCallManager", "Invalid default audio device selection");
|
|
1589
907
|
break;
|
|
1590
908
|
}
|
|
1591
|
-
|
|
909
|
+
RTKLogger.d("InCallManager", "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
|
|
1592
910
|
updateAudioDeviceState();
|
|
1593
911
|
}
|
|
1594
912
|
|
|
1595
913
|
/** Changes selection of the currently active audio device. */
|
|
1596
914
|
public void selectAudioDevice(AudioDevice device) {
|
|
1597
|
-
|
|
915
|
+
RTKLogger.d("InCallManager", "Selecting " + device.name());
|
|
1598
916
|
userSelectedAudioDevice = device;
|
|
1599
917
|
updateAudioDeviceState();
|
|
1600
918
|
if (device != AudioDevice.NONE && !audioDevices.contains(device)) {
|
|
1601
|
-
|
|
919
|
+
RTKLogger.e("InCallManager", "selectAudioDevice() Can not select " + device + " from available " + audioDevices);
|
|
1602
920
|
return;
|
|
1603
921
|
}
|
|
1604
922
|
}
|
|
@@ -1632,34 +950,12 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1632
950
|
try {
|
|
1633
951
|
reactContext.unregisterReceiver(receiver);
|
|
1634
952
|
} catch (final Exception e) {
|
|
1635
|
-
|
|
953
|
+
RTKLogger.d("InCallManager", "unregisterReceiver() failed");
|
|
1636
954
|
}
|
|
1637
955
|
} else {
|
|
1638
|
-
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
/** Sets the speaker phone mode. */
|
|
1643
|
-
/*
|
|
1644
|
-
private void setSpeakerphoneOn(boolean on) {
|
|
1645
|
-
boolean wasOn = audioManager.isSpeakerphoneOn();
|
|
1646
|
-
if (wasOn == on) {
|
|
1647
|
-
return;
|
|
1648
|
-
}
|
|
1649
|
-
audioManager.setSpeakerphoneOn(on);
|
|
1650
|
-
}
|
|
1651
|
-
*/
|
|
1652
|
-
|
|
1653
|
-
/** Sets the microphone mute state. */
|
|
1654
|
-
/*
|
|
1655
|
-
private void setMicrophoneMute(boolean on) {
|
|
1656
|
-
boolean wasMuted = audioManager.isMicrophoneMute();
|
|
1657
|
-
if (wasMuted == on) {
|
|
1658
|
-
return;
|
|
956
|
+
RTKLogger.d("InCallManager", "unregisterReceiver() reactContext is null");
|
|
1659
957
|
}
|
|
1660
|
-
audioManager.setMicrophoneMute(on);
|
|
1661
958
|
}
|
|
1662
|
-
*/
|
|
1663
959
|
|
|
1664
960
|
/** Gets the current earpiece state. */
|
|
1665
961
|
private boolean hasEarpiece() {
|
|
@@ -1682,13 +978,13 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1682
978
|
for (AudioDeviceInfo device : devices) {
|
|
1683
979
|
final int type = device.getType();
|
|
1684
980
|
if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
|
|
1685
|
-
|
|
981
|
+
RTKLogger.d("InCallManager", "hasWiredHeadset: found wired headset");
|
|
1686
982
|
return true;
|
|
1687
983
|
} else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
|
|
1688
|
-
|
|
984
|
+
RTKLogger.d("InCallManager", "hasWiredHeadset: found USB audio device");
|
|
1689
985
|
return true;
|
|
1690
986
|
} else if (type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
|
|
1691
|
-
|
|
987
|
+
RTKLogger.d("InCallManager", "hasWiredHeadset: found wired headphones");
|
|
1692
988
|
return true;
|
|
1693
989
|
}
|
|
1694
990
|
}
|
|
@@ -1709,14 +1005,14 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1709
1005
|
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
|
|
1710
1006
|
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) {
|
|
1711
1007
|
promise.resolve(true);
|
|
1712
|
-
|
|
1008
|
+
RTKLogger.i("InCallManager", "isBluetoothHeadsetConnected: true");
|
|
1713
1009
|
} else {
|
|
1714
1010
|
promise.resolve(false);
|
|
1715
|
-
|
|
1011
|
+
RTKLogger.i("InCallManager", "isBluetoothHeadsetConnected: false");
|
|
1716
1012
|
}
|
|
1717
1013
|
} catch (Exception e) {
|
|
1718
1014
|
promise.resolve(false);
|
|
1719
|
-
|
|
1015
|
+
RTKLogger.e("InCallManager", "isBluetoothHeadsetConnected: false due to: " + e.getMessage(), e);
|
|
1720
1016
|
}
|
|
1721
1017
|
});
|
|
1722
1018
|
}
|
|
@@ -1727,17 +1023,17 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1727
1023
|
*/
|
|
1728
1024
|
public void updateAudioDeviceState() {
|
|
1729
1025
|
UiThreadUtil.runOnUiThread(() -> {
|
|
1730
|
-
|
|
1026
|
+
RTKLogger.d("InCallManager", "Bluetooth State: " + bluetoothManager.getState().toString());
|
|
1731
1027
|
if(bluetoothManager.getState().toString().equals("UNINITIALIZED"))
|
|
1732
1028
|
bluetoothManager.start();
|
|
1733
1029
|
hasWiredHeadset= this.hasWiredHeadset();
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1030
|
+
RTKLogger.d("InCallManager", "--- updateAudioDeviceState: "
|
|
1031
|
+
+ "wired headset=" + hasWiredHeadset + ", "
|
|
1032
|
+
+ "BT state=" + bluetoothManager.getState());
|
|
1033
|
+
RTKLogger.d("InCallManager", "Device status: "
|
|
1034
|
+
+ "available=" + audioDevices + ", "
|
|
1035
|
+
+ "selected=" + selectedAudioDevice + ", "
|
|
1036
|
+
+ "user selected=" + userSelectedAudioDevice);
|
|
1741
1037
|
|
|
1742
1038
|
// Check if any Bluetooth headset is connected. The internal BT state will
|
|
1743
1039
|
// change accordingly.
|
|
@@ -1792,23 +1088,30 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1792
1088
|
}
|
|
1793
1089
|
|
|
1794
1090
|
// --- start bluetooth if needed
|
|
1795
|
-
if
|
|
1796
|
-
|
|
1091
|
+
// Start SCO if: switching to Bluetooth OR already on Bluetooth but SCO not connected yet
|
|
1092
|
+
if (newAudioDevice == AudioDevice.BLUETOOTH
|
|
1797
1093
|
&& bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) {
|
|
1798
1094
|
// Attempt to start Bluetooth SCO audio (takes a few second to start).
|
|
1095
|
+
RTKLogger.d("InCallManager", "Starting Bluetooth SCO audio...");
|
|
1799
1096
|
if (!bluetoothManager.startScoAudio()) {
|
|
1800
1097
|
// Remove BLUETOOTH from list of available devices since SCO failed.
|
|
1098
|
+
RTKLogger.e("InCallManager", "Failed to start Bluetooth SCO audio");
|
|
1801
1099
|
audioDevices.remove(AudioDevice.BLUETOOTH);
|
|
1802
1100
|
audioDeviceSetUpdated = true;
|
|
1803
1101
|
if (userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
|
|
1804
1102
|
userSelectedAudioDevice = AudioDevice.NONE;
|
|
1805
1103
|
}
|
|
1806
1104
|
newAudioDevice = getPreferredAudioDevice();
|
|
1105
|
+
} else {
|
|
1106
|
+
RTKLogger.d("InCallManager", "Bluetooth SCO audio start initiated");
|
|
1807
1107
|
}
|
|
1808
1108
|
}
|
|
1809
1109
|
|
|
1110
|
+
// Only fall back from Bluetooth if user didn't explicitly select it
|
|
1111
|
+
// If user selected Bluetooth, wait for SCO connection instead of falling back
|
|
1810
1112
|
if (newAudioDevice == AudioDevice.BLUETOOTH
|
|
1811
|
-
&& bluetoothManager.getState() != AppRTCBluetoothManager.State.SCO_CONNECTED
|
|
1113
|
+
&& bluetoothManager.getState() != AppRTCBluetoothManager.State.SCO_CONNECTED
|
|
1114
|
+
&& userSelectedAudioDevice != AudioDevice.BLUETOOTH) {
|
|
1812
1115
|
newAudioDevice = getPreferredAudioDevice(true); // --- skip bluetooth
|
|
1813
1116
|
}
|
|
1814
1117
|
|
|
@@ -1817,9 +1120,9 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1817
1120
|
|
|
1818
1121
|
// Do the required device switch.
|
|
1819
1122
|
setAudioDeviceInternal(newAudioDevice);
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1123
|
+
RTKLogger.d("InCallManager", "New device status: "
|
|
1124
|
+
+ "available=" + audioDevices + ", "
|
|
1125
|
+
+ "selected=" + newAudioDevice);
|
|
1823
1126
|
/*
|
|
1824
1127
|
if (audioManagerEvents != null) {
|
|
1825
1128
|
// Notify a listening client that audio device has been changed.
|
|
@@ -1828,7 +1131,7 @@ public class InCallManagerModule extends ReactContextBaseJavaModule implements L
|
|
|
1828
1131
|
*/
|
|
1829
1132
|
sendEvent("onAudioDeviceChanged", getAudioDeviceStatusMap());
|
|
1830
1133
|
}
|
|
1831
|
-
|
|
1134
|
+
RTKLogger.d("InCallManager", "--- updateAudioDeviceState done");
|
|
1832
1135
|
});
|
|
1833
1136
|
}
|
|
1834
1137
|
|