@cloudflare/realtimekit-react-native 0.0.0 → 0.0.1-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +83 -0
- package/RealtimeKitCore.podspec +19 -0
- package/android/build.gradle +58 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/android/gradlew +185 -0
- package/android/gradlew.bat +89 -0
- package/android/src/main/AndroidManifest.xml +23 -0
- package/android/src/main/java/com/reactnativecore/BackgroundTimer/RNBackgroundTimer.java +97 -0
- package/android/src/main/java/com/reactnativecore/Constants.java +14 -0
- package/android/src/main/java/com/reactnativecore/CoreModule.java +49 -0
- package/android/src/main/java/com/reactnativecore/CorePackage.java +34 -0
- package/android/src/main/java/com/reactnativecore/DyteHelperModule.java +268 -0
- package/android/src/main/java/com/reactnativecore/DyteHolder.java +7 -0
- package/android/src/main/java/com/reactnativecore/ForegroundService.java +48 -0
- package/android/src/main/java/com/reactnativecore/NotificationHelper.java +127 -0
- package/android/src/main/java/com/reactnativecore/RNPermissionsModule.java +226 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCBluetoothManager.java +637 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCProximitySensor.java +133 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/AppRTCUtils.java +41 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/InCallManagerModule.java +1881 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/InCallProximityManager.java +162 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/InCallWakeLockUtils.java +116 -0
- package/android/src/main/java/com/reactnativecore/incallmanager/ThreadUtils.java +39 -0
- package/app.plugin.js +1 -0
- package/ios/BroadcastEventEmitter.h +11 -0
- package/ios/BroadcastEventEmitter.m +72 -0
- package/ios/Core.h +5 -0
- package/ios/Core.m +28 -0
- package/ios/Core.xcodeproj/project.pbxproj +287 -0
- package/ios/DyteRNBackgroundTimer.h +22 -0
- package/ios/DyteRNBackgroundTimer.m +90 -0
- package/ios/DyteRNPermissionHandlerNotifications.h +17 -0
- package/ios/DyteRNPermissionHandlerNotifications.m +127 -0
- package/ios/DyteRNPermissions.h +50 -0
- package/ios/DyteRNPermissions.m +275 -0
- package/ios/DyteScreensharePickerView.h +8 -0
- package/ios/DyteScreensharePickerView.m +27 -0
- package/ios/PrivacyInfo.xcprivacy +60 -0
- package/ios/RNInCallManager.h +16 -0
- package/ios/RNInCallManager.m +1354 -0
- package/ios/permissions/DyteRNPermissionHandlerBluetoothPeripheral.h +4 -0
- package/ios/permissions/DyteRNPermissionHandlerBluetoothPeripheral.m +87 -0
- package/ios/permissions/DyteRNPermissionHandlerCamera.h +5 -0
- package/ios/permissions/DyteRNPermissionHandlerCamera.m +38 -0
- package/ios/permissions/DyteRNPermissionHandlerMicrophone.h +5 -0
- package/ios/permissions/DyteRNPermissionHandlerMicrophone.m +34 -0
- package/ios/permissions/DyteRNPermissionHandlerPhotoLibrary.h +8 -0
- package/ios/permissions/DyteRNPermissionHandlerPhotoLibrary.m +70 -0
- package/ios/screenshare/DyteDarwinNotificationCenter.swift +21 -0
- package/ios/screenshare/DyteScreenshareAtomic.swift +29 -0
- package/ios/screenshare/DyteScreenshareHandler.swift +103 -0
- package/ios/screenshare/DyteScreenshareUploader.swift +139 -0
- package/ios/screenshare/DyteSocketConnection.swift +190 -0
- package/lib/BackgroundHandler.d.ts +20 -0
- package/lib/BackgroundHandler.js +87 -0
- package/lib/LocalMediaError.d.ts +4 -0
- package/lib/LocalMediaError.js +6 -0
- package/lib/LocalMediaHandler.d.ts +118 -0
- package/lib/LocalMediaHandler.js +853 -0
- package/lib/LocalMediaInterfaces.d.ts +76 -0
- package/lib/LocalMediaInterfaces.js +1 -0
- package/lib/LocalMediaUtils.d.ts +93 -0
- package/lib/LocalMediaUtils.js +276 -0
- package/lib/NativeAudioManager.d.ts +16 -0
- package/lib/NativeAudioManager.js +93 -0
- package/lib/PermissionHandler.d.ts +34 -0
- package/lib/PermissionHandler.js +179 -0
- package/lib/ReactContext.d.ts +42 -0
- package/lib/ReactContext.js +96 -0
- package/lib/ReactHooks.d.ts +11 -0
- package/lib/ReactHooks.js +73 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +40 -0
- package/lib/utils/crypto.d.ts +17 -0
- package/lib/utils/crypto.js +82 -0
- package/package.json +51 -4
- package/plugin/build/withDyte.d.ts +25 -0
- package/plugin/build/withDyte.js +100 -0
- package/plugin/src/withDyte.ts +150 -0
- package/plugin/tsconfig.json +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,1881 @@
|
|
|
1
|
+
package com.reactnativecore.incallmanager;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.content.IntentFilter;
|
|
7
|
+
import android.content.BroadcastReceiver;
|
|
8
|
+
import android.content.pm.PackageManager;
|
|
9
|
+
import android.media.AudioAttributes;
|
|
10
|
+
import android.media.AudioDeviceInfo;
|
|
11
|
+
import android.media.AudioFocusRequest;
|
|
12
|
+
import android.media.AudioManager;
|
|
13
|
+
import android.media.MediaPlayer;
|
|
14
|
+
import android.media.ToneGenerator;
|
|
15
|
+
import android.net.Uri;
|
|
16
|
+
import android.os.PowerManager;
|
|
17
|
+
import android.os.Build;
|
|
18
|
+
import android.os.Handler;
|
|
19
|
+
import android.os.Looper;
|
|
20
|
+
import android.provider.Settings;
|
|
21
|
+
import androidx.annotation.Nullable;
|
|
22
|
+
import android.util.Log;
|
|
23
|
+
import android.util.SparseArray;
|
|
24
|
+
import android.view.Display;
|
|
25
|
+
import android.view.KeyEvent;
|
|
26
|
+
import android.view.Window;
|
|
27
|
+
import android.view.WindowManager;
|
|
28
|
+
import android.media.AudioDeviceInfo;
|
|
29
|
+
import android.media.MediaPlayer;
|
|
30
|
+
|
|
31
|
+
import com.facebook.react.bridge.Arguments;
|
|
32
|
+
import com.facebook.react.bridge.LifecycleEventListener;
|
|
33
|
+
import com.facebook.react.bridge.NativeModule;
|
|
34
|
+
import com.facebook.react.bridge.Promise;
|
|
35
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
36
|
+
import com.facebook.react.bridge.ReactContext;
|
|
37
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
38
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
39
|
+
import com.facebook.react.bridge.UiThreadUtil;
|
|
40
|
+
import com.facebook.react.bridge.WritableMap;
|
|
41
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
42
|
+
|
|
43
|
+
import java.lang.Runnable;
|
|
44
|
+
import java.io.File;
|
|
45
|
+
import java.util.Collections;
|
|
46
|
+
import java.util.Map;
|
|
47
|
+
import java.util.HashMap;
|
|
48
|
+
import java.util.HashSet;
|
|
49
|
+
import java.util.Random;
|
|
50
|
+
import java.util.Set;
|
|
51
|
+
import java.util.List;
|
|
52
|
+
|
|
53
|
+
import com.reactnativecore.incallmanager.AppRTCBluetoothManager;
|
|
54
|
+
import com.reactnativecore.incallmanager.InCallManagerModule.AudioDevice;
|
|
55
|
+
|
|
56
|
+
public class InCallManagerModule extends ReactContextBaseJavaModule implements LifecycleEventListener, AudioManager.OnAudioFocusChangeListener {
|
|
57
|
+
private static final String REACT_NATIVE_MODULE_NAME = "InCallManager";
|
|
58
|
+
private static final String TAG = REACT_NATIVE_MODULE_NAME;
|
|
59
|
+
private String mPackageName = "com.reactnativecore.incallmanager";
|
|
60
|
+
|
|
61
|
+
// --- Screen Manager
|
|
62
|
+
private PowerManager mPowerManager;
|
|
63
|
+
private WindowManager.LayoutParams lastLayoutParams;
|
|
64
|
+
private WindowManager mWindowManager;
|
|
65
|
+
|
|
66
|
+
// --- AudioRouteManager
|
|
67
|
+
private AudioManager audioManager;
|
|
68
|
+
private boolean audioManagerActivated = false;
|
|
69
|
+
private boolean isAudioFocused = false;
|
|
70
|
+
//private final Object mAudioFocusLock = new Object();
|
|
71
|
+
private boolean isOrigAudioSetupStored = false;
|
|
72
|
+
private boolean origIsSpeakerPhoneOn = false;
|
|
73
|
+
private boolean origIsMicrophoneMute = false;
|
|
74
|
+
private int origAudioMode = AudioManager.MODE_INVALID;
|
|
75
|
+
private boolean defaultSpeakerOn = false;
|
|
76
|
+
private int defaultAudioMode = AudioManager.MODE_IN_COMMUNICATION;
|
|
77
|
+
private int forceSpeakerOn = 0;
|
|
78
|
+
private boolean automatic = true;
|
|
79
|
+
private boolean isProximityRegistered = false;
|
|
80
|
+
private boolean proximityIsNear = false;
|
|
81
|
+
private static final String ACTION_HEADSET_PLUG = (android.os.Build.VERSION.SDK_INT >= 21) ? AudioManager.ACTION_HEADSET_PLUG : Intent.ACTION_HEADSET_PLUG;
|
|
82
|
+
private BroadcastReceiver wiredHeadsetReceiver;
|
|
83
|
+
private BroadcastReceiver noisyAudioReceiver;
|
|
84
|
+
private BroadcastReceiver mediaButtonReceiver;
|
|
85
|
+
private AudioAttributes mAudioAttributes;
|
|
86
|
+
private AudioFocusRequest mAudioFocusRequest;
|
|
87
|
+
|
|
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
|
+
/**
|
|
108
|
+
* AudioDevice is the names of possible audio devices that we currently
|
|
109
|
+
* support.
|
|
110
|
+
*/
|
|
111
|
+
public enum AudioDevice { SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE }
|
|
112
|
+
|
|
113
|
+
/** AudioManager state. */
|
|
114
|
+
public enum AudioManagerState {
|
|
115
|
+
UNINITIALIZED,
|
|
116
|
+
PREINITIALIZED,
|
|
117
|
+
RUNNING,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private int savedAudioMode = AudioManager.MODE_INVALID;
|
|
121
|
+
private boolean savedIsSpeakerPhoneOn = false;
|
|
122
|
+
private boolean savedIsMicrophoneMute = false;
|
|
123
|
+
private boolean hasWiredHeadset = false;
|
|
124
|
+
|
|
125
|
+
// Default audio device; speaker phone for video calls or earpiece for audio
|
|
126
|
+
// only calls.
|
|
127
|
+
private AudioDevice defaultAudioDevice = AudioDevice.NONE;
|
|
128
|
+
|
|
129
|
+
// Contains the currently selected audio device.
|
|
130
|
+
// This device is changed automatically using a certain scheme where e.g.
|
|
131
|
+
// a wired headset "wins" over speaker phone. It is also possible for a
|
|
132
|
+
// user to explicitly select a device (and overrid any predefined scheme).
|
|
133
|
+
// See |userSelectedAudioDevice| for details.
|
|
134
|
+
private AudioDevice selectedAudioDevice;
|
|
135
|
+
|
|
136
|
+
// Contains the user-selected audio device which overrides the predefined
|
|
137
|
+
// selection scheme.
|
|
138
|
+
// TODO(henrika): always set to AudioDevice.NONE today. Add support for
|
|
139
|
+
// explicit selection based on choice by userSelectedAudioDevice.
|
|
140
|
+
private AudioDevice userSelectedAudioDevice;
|
|
141
|
+
|
|
142
|
+
// Contains speakerphone setting: auto, true or false
|
|
143
|
+
private final String useSpeakerphone = SPEAKERPHONE_AUTO;
|
|
144
|
+
|
|
145
|
+
// Handles all tasks related to Bluetooth headset devices.
|
|
146
|
+
private AppRTCBluetoothManager bluetoothManager = null;
|
|
147
|
+
|
|
148
|
+
private final InCallProximityManager proximityManager;
|
|
149
|
+
|
|
150
|
+
private final InCallWakeLockUtils wakeLockUtils;
|
|
151
|
+
|
|
152
|
+
// Contains a list of available audio devices. A Set collection is used to
|
|
153
|
+
// avoid duplicate elements.
|
|
154
|
+
private Set<AudioDevice> audioDevices = new HashSet<>();
|
|
155
|
+
|
|
156
|
+
interface MyPlayerInterface {
|
|
157
|
+
public boolean isPlaying();
|
|
158
|
+
public void startPlay(Map<String, Object> data);
|
|
159
|
+
public void stopPlay();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Override
|
|
163
|
+
public String getName() {
|
|
164
|
+
return REACT_NATIVE_MODULE_NAME;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public InCallManagerModule(ReactApplicationContext reactContext) {
|
|
168
|
+
super(reactContext);
|
|
169
|
+
mPackageName = reactContext.getPackageName();
|
|
170
|
+
reactContext.addLifecycleEventListener(this);
|
|
171
|
+
mWindowManager = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE);
|
|
172
|
+
mPowerManager = (PowerManager) reactContext.getSystemService(Context.POWER_SERVICE);
|
|
173
|
+
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
|
+
wakeLockUtils = new InCallWakeLockUtils(reactContext);
|
|
182
|
+
proximityManager = InCallProximityManager.create(reactContext, this);
|
|
183
|
+
|
|
184
|
+
UiThreadUtil.runOnUiThread(() -> {
|
|
185
|
+
bluetoothManager = AppRTCBluetoothManager.create(reactContext, this);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Log.d(TAG, "InCallManager initialized");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private void manualTurnScreenOff() {
|
|
192
|
+
// Log.d(TAG, "manualTurnScreenOff()");
|
|
193
|
+
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
194
|
+
public void run() {
|
|
195
|
+
Activity mCurrentActivity = getCurrentActivity();
|
|
196
|
+
if (mCurrentActivity == null) {
|
|
197
|
+
// Log.d(TAG, "ReactContext doesn't hava any Activity attached.");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
Window window = mCurrentActivity.getWindow();
|
|
201
|
+
WindowManager.LayoutParams params = window.getAttributes();
|
|
202
|
+
lastLayoutParams = params; // --- store last param
|
|
203
|
+
params.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF; // --- Dim as dark as possible. see BRIGHTNESS_OVERRIDE_OFF
|
|
204
|
+
window.setAttributes(params);
|
|
205
|
+
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private void manualTurnScreenOn() {
|
|
211
|
+
// Log.d(TAG, "manualTurnScreenOn()");
|
|
212
|
+
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
213
|
+
public void run() {
|
|
214
|
+
Activity mCurrentActivity = getCurrentActivity();
|
|
215
|
+
if (mCurrentActivity == null) {
|
|
216
|
+
// Log.d(TAG, "ReactContext doesn't hava any Activity attached.");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
Window window = mCurrentActivity.getWindow();
|
|
220
|
+
if (lastLayoutParams != null) {
|
|
221
|
+
window.setAttributes(lastLayoutParams);
|
|
222
|
+
} else {
|
|
223
|
+
WindowManager.LayoutParams params = window.getAttributes();
|
|
224
|
+
params.screenBrightness = -1; // --- Dim to preferable one
|
|
225
|
+
window.setAttributes(params);
|
|
226
|
+
}
|
|
227
|
+
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private void storeOriginalAudioSetup() {
|
|
233
|
+
// Log.d(TAG, "storeOriginalAudioSetup()");
|
|
234
|
+
if (!isOrigAudioSetupStored) {
|
|
235
|
+
origAudioMode = audioManager.getMode();
|
|
236
|
+
origIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
|
|
237
|
+
origIsMicrophoneMute = audioManager.isMicrophoneMute();
|
|
238
|
+
isOrigAudioSetupStored = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private void restoreOriginalAudioSetup() {
|
|
243
|
+
// Log.d(TAG, "restoreOriginalAudioSetup()");
|
|
244
|
+
if (isOrigAudioSetupStored) {
|
|
245
|
+
setSpeakerphoneOn(origIsSpeakerPhoneOn);
|
|
246
|
+
setMicrophoneMute(origIsMicrophoneMute);
|
|
247
|
+
audioManager.setMode(origAudioMode);
|
|
248
|
+
if (getCurrentActivity() != null) {
|
|
249
|
+
getCurrentActivity().setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
|
|
250
|
+
}
|
|
251
|
+
isOrigAudioSetupStored = false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private void startWiredHeadsetEvent() {
|
|
256
|
+
if (wiredHeadsetReceiver == null) {
|
|
257
|
+
// Log.d(TAG, "startWiredHeadsetEvent()");
|
|
258
|
+
IntentFilter filter = new IntentFilter(ACTION_HEADSET_PLUG);
|
|
259
|
+
wiredHeadsetReceiver = new BroadcastReceiver() {
|
|
260
|
+
@Override
|
|
261
|
+
public void onReceive(Context context, Intent intent) {
|
|
262
|
+
if (ACTION_HEADSET_PLUG.equals(intent.getAction())) {
|
|
263
|
+
hasWiredHeadset = intent.getIntExtra("state", 0) == 1;
|
|
264
|
+
updateAudioRoute();
|
|
265
|
+
String deviceName = intent.getStringExtra("name");
|
|
266
|
+
if (deviceName == null) {
|
|
267
|
+
deviceName = "";
|
|
268
|
+
}
|
|
269
|
+
WritableMap data = Arguments.createMap();
|
|
270
|
+
data.putBoolean("isPlugged", (intent.getIntExtra("state", 0) == 1) ? true : false);
|
|
271
|
+
data.putBoolean("hasMic", (intent.getIntExtra("microphone", 0) == 1) ? true : false);
|
|
272
|
+
data.putString("deviceName", deviceName);
|
|
273
|
+
sendEvent("WiredHeadset", data);
|
|
274
|
+
} else {
|
|
275
|
+
hasWiredHeadset = false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
ReactContext reactContext = getReactApplicationContext();
|
|
280
|
+
if (reactContext != null) {
|
|
281
|
+
this.registerReceiver(wiredHeadsetReceiver, filter);
|
|
282
|
+
} else {
|
|
283
|
+
// Log.d(TAG, "startWiredHeadsetEvent() reactContext is null");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private void stopWiredHeadsetEvent() {
|
|
289
|
+
if (wiredHeadsetReceiver != null) {
|
|
290
|
+
// Log.d(TAG, "stopWiredHeadsetEvent()");
|
|
291
|
+
this.unregisterReceiver(this.wiredHeadsetReceiver);
|
|
292
|
+
wiredHeadsetReceiver = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private void startNoisyAudioEvent() {
|
|
297
|
+
if (noisyAudioReceiver == null) {
|
|
298
|
+
// Log.d(TAG, "startNoisyAudioEvent()");
|
|
299
|
+
IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
|
300
|
+
noisyAudioReceiver = new BroadcastReceiver() {
|
|
301
|
+
@Override
|
|
302
|
+
public void onReceive(Context context, Intent intent) {
|
|
303
|
+
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
|
|
304
|
+
updateAudioRoute();
|
|
305
|
+
sendEvent("NoisyAudio", null);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
ReactContext reactContext = getReactApplicationContext();
|
|
310
|
+
if (reactContext != null) {
|
|
311
|
+
this.registerReceiver(noisyAudioReceiver, filter);
|
|
312
|
+
} else {
|
|
313
|
+
// Log.d(TAG, "startNoisyAudioEvent() reactContext is null");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private void stopNoisyAudioEvent() {
|
|
319
|
+
if (noisyAudioReceiver != null) {
|
|
320
|
+
// Log.d(TAG, "stopNoisyAudioEvent()");
|
|
321
|
+
this.unregisterReceiver(this.noisyAudioReceiver);
|
|
322
|
+
noisyAudioReceiver = null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
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
|
+
public void onProximitySensorChangedState(boolean isNear) {
|
|
394
|
+
if (automatic && getSelectedAudioDevice() == AudioDevice.EARPIECE) {
|
|
395
|
+
if (isNear) {
|
|
396
|
+
turnScreenOff();
|
|
397
|
+
} else {
|
|
398
|
+
turnScreenOn();
|
|
399
|
+
}
|
|
400
|
+
updateAudioRoute();
|
|
401
|
+
}
|
|
402
|
+
WritableMap data = Arguments.createMap();
|
|
403
|
+
data.putBoolean("isNear", isNear);
|
|
404
|
+
sendEvent("Proximity", data);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@ReactMethod
|
|
408
|
+
public void startProximitySensor() {
|
|
409
|
+
if (!proximityManager.isProximitySupported()) {
|
|
410
|
+
// Log.d(TAG, "Proximity Sensor is not supported.");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (isProximityRegistered) {
|
|
414
|
+
// Log.d(TAG, "Proximity Sensor is already registered.");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
// --- SENSOR_DELAY_FASTEST(0 milisecs), SENSOR_DELAY_GAME(20 milisecs), SENSOR_DELAY_UI(60 milisecs), SENSOR_DELAY_NORMAL(200 milisecs)
|
|
418
|
+
if (!proximityManager.start()) {
|
|
419
|
+
// Log.d(TAG, "proximityManager.start() failed. return false");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
// Log.d(TAG, "startProximitySensor()");
|
|
423
|
+
isProximityRegistered = true;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@ReactMethod
|
|
427
|
+
public void stopProximitySensor() {
|
|
428
|
+
if (!proximityManager.isProximitySupported()) {
|
|
429
|
+
// Log.d(TAG, "Proximity Sensor is not supported.");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (!isProximityRegistered) {
|
|
433
|
+
// Log.d(TAG, "Proximity Sensor is not registered.");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
// Log.d(TAG, "stopProximitySensor()");
|
|
437
|
+
proximityManager.stop();
|
|
438
|
+
isProximityRegistered = false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// --- see: https://developer.android.com/reference/android/media/AudioManager
|
|
442
|
+
@Override
|
|
443
|
+
public void onAudioFocusChange(int focusChange) {
|
|
444
|
+
String focusChangeStr;
|
|
445
|
+
switch (focusChange) {
|
|
446
|
+
case AudioManager.AUDIOFOCUS_GAIN:
|
|
447
|
+
focusChangeStr = "AUDIOFOCUS_GAIN";
|
|
448
|
+
break;
|
|
449
|
+
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
|
|
450
|
+
focusChangeStr = "AUDIOFOCUS_GAIN_TRANSIENT";
|
|
451
|
+
break;
|
|
452
|
+
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
|
|
453
|
+
focusChangeStr = "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE";
|
|
454
|
+
break;
|
|
455
|
+
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
|
|
456
|
+
focusChangeStr = "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK";
|
|
457
|
+
break;
|
|
458
|
+
case AudioManager.AUDIOFOCUS_LOSS:
|
|
459
|
+
focusChangeStr = "AUDIOFOCUS_LOSS";
|
|
460
|
+
break;
|
|
461
|
+
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
462
|
+
focusChangeStr = "AUDIOFOCUS_LOSS_TRANSIENT";
|
|
463
|
+
break;
|
|
464
|
+
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
|
465
|
+
focusChangeStr = "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK";
|
|
466
|
+
break;
|
|
467
|
+
case AudioManager.AUDIOFOCUS_NONE:
|
|
468
|
+
focusChangeStr = "AUDIOFOCUS_NONE";
|
|
469
|
+
break;
|
|
470
|
+
default:
|
|
471
|
+
focusChangeStr = "AUDIOFOCUS_UNKNOW";
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Log.d(TAG, "onAudioFocusChange(): " + focusChange + " - " + focusChangeStr);
|
|
476
|
+
|
|
477
|
+
WritableMap data = Arguments.createMap();
|
|
478
|
+
data.putString("eventText", focusChangeStr);
|
|
479
|
+
data.putInt("eventCode", focusChange);
|
|
480
|
+
sendEvent("onAudioFocusChange", data);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private void sendEvent(final String eventName, @Nullable WritableMap params) {
|
|
484
|
+
try {
|
|
485
|
+
ReactContext reactContext = getReactApplicationContext();
|
|
486
|
+
if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
|
|
487
|
+
// Log.i(TAG, "Sending event");
|
|
488
|
+
reactContext
|
|
489
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
490
|
+
.emit(eventName, params);
|
|
491
|
+
} else {
|
|
492
|
+
Log.e(TAG, "sendEvent(): reactContext is null or not having CatalystInstance yet.");
|
|
493
|
+
}
|
|
494
|
+
} catch (RuntimeException e) {
|
|
495
|
+
Log.e(TAG, "sendEvent(): java.lang.RuntimeException: Trying to invoke JS before CatalystInstance has been set!");
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@ReactMethod
|
|
500
|
+
public void start(final String _media, final boolean auto, final String ringbackUriType) {
|
|
501
|
+
media = _media;
|
|
502
|
+
if (media.equals("video")) {
|
|
503
|
+
defaultSpeakerOn = true;
|
|
504
|
+
} else {
|
|
505
|
+
defaultSpeakerOn = false;
|
|
506
|
+
}
|
|
507
|
+
automatic = auto;
|
|
508
|
+
if (!audioManagerActivated) {
|
|
509
|
+
audioManagerActivated = true;
|
|
510
|
+
|
|
511
|
+
// Log.d(TAG, "start audioRouteManager");
|
|
512
|
+
wakeLockUtils.acquirePartialWakeLock();
|
|
513
|
+
if (mRingtone != null && mRingtone.isPlaying()) {
|
|
514
|
+
// Log.d(TAG, "stop ringtone");
|
|
515
|
+
stopRingtone(); // --- use brandnew instance
|
|
516
|
+
}
|
|
517
|
+
storeOriginalAudioSetup();
|
|
518
|
+
requestAudioFocus();
|
|
519
|
+
startEvents();
|
|
520
|
+
UiThreadUtil.runOnUiThread(() -> {
|
|
521
|
+
bluetoothManager.start();
|
|
522
|
+
});
|
|
523
|
+
audioManager.setMode(defaultAudioMode);
|
|
524
|
+
setSpeakerphoneOn(defaultSpeakerOn);
|
|
525
|
+
setMicrophoneMute(false);
|
|
526
|
+
forceSpeakerOn = 0;
|
|
527
|
+
hasWiredHeadset = hasWiredHeadset();
|
|
528
|
+
defaultAudioDevice = (defaultSpeakerOn) ? AudioDevice.SPEAKER_PHONE : (hasEarpiece()) ? AudioDevice.EARPIECE : AudioDevice.SPEAKER_PHONE;
|
|
529
|
+
userSelectedAudioDevice = AudioDevice.NONE;
|
|
530
|
+
selectedAudioDevice = AudioDevice.NONE;
|
|
531
|
+
audioDevices.clear();
|
|
532
|
+
updateAudioRoute();
|
|
533
|
+
|
|
534
|
+
if (!ringbackUriType.isEmpty()) {
|
|
535
|
+
startRingback(ringbackUriType);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
public void stop() {
|
|
541
|
+
stop("");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
@ReactMethod
|
|
545
|
+
public void stop(final String busytoneUriType) {
|
|
546
|
+
if (audioManagerActivated) {
|
|
547
|
+
stopRingback();
|
|
548
|
+
if (!busytoneUriType.isEmpty() && startBusytone(busytoneUriType)) {
|
|
549
|
+
// play busytone first, and call this func again when finish
|
|
550
|
+
// Log.d(TAG, "play busytone before stop InCallManager");
|
|
551
|
+
return;
|
|
552
|
+
} else {
|
|
553
|
+
// Log.d(TAG, "stop() InCallManager");
|
|
554
|
+
stopBusytone();
|
|
555
|
+
stopEvents();
|
|
556
|
+
setSpeakerphoneOn(false);
|
|
557
|
+
setMicrophoneMute(false);
|
|
558
|
+
forceSpeakerOn = 0;
|
|
559
|
+
UiThreadUtil.runOnUiThread(() -> {
|
|
560
|
+
bluetoothManager.stop();
|
|
561
|
+
});
|
|
562
|
+
restoreOriginalAudioSetup();
|
|
563
|
+
abandonAudioFocus();
|
|
564
|
+
audioManagerActivated = false;
|
|
565
|
+
}
|
|
566
|
+
wakeLockUtils.releasePartialWakeLock();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private void startEvents() {
|
|
571
|
+
startWiredHeadsetEvent();
|
|
572
|
+
startNoisyAudioEvent();
|
|
573
|
+
startMediaButtonEvent();
|
|
574
|
+
startProximitySensor(); // --- proximity event always enable, but only turn screen off when audio is routing to earpiece.
|
|
575
|
+
setKeepScreenOn(true);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private void stopEvents() {
|
|
579
|
+
stopWiredHeadsetEvent();
|
|
580
|
+
stopNoisyAudioEvent();
|
|
581
|
+
stopMediaButtonEvent();
|
|
582
|
+
stopProximitySensor();
|
|
583
|
+
setKeepScreenOn(false);
|
|
584
|
+
turnScreenOn();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
@ReactMethod
|
|
588
|
+
public void requestAudioFocusJS(Promise promise) {
|
|
589
|
+
promise.resolve(requestAudioFocus());
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private String requestAudioFocus() {
|
|
593
|
+
String requestAudioFocusResStr = (android.os.Build.VERSION.SDK_INT >= 26)
|
|
594
|
+
? requestAudioFocusV26()
|
|
595
|
+
: requestAudioFocusOld();
|
|
596
|
+
// Log.d(TAG, "requestAudioFocus(): res = " + requestAudioFocusResStr);
|
|
597
|
+
return requestAudioFocusResStr;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private String requestAudioFocusV26() {
|
|
601
|
+
if (isAudioFocused) {
|
|
602
|
+
return "";
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (mAudioAttributes == null) {
|
|
606
|
+
mAudioAttributes = new AudioAttributes.Builder()
|
|
607
|
+
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
|
|
608
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
609
|
+
.build();
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (mAudioFocusRequest == null) {
|
|
613
|
+
mAudioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
|
|
614
|
+
.setAudioAttributes(mAudioAttributes)
|
|
615
|
+
.setAcceptsDelayedFocusGain(false)
|
|
616
|
+
.setWillPauseWhenDucked(false)
|
|
617
|
+
.setOnAudioFocusChangeListener(this)
|
|
618
|
+
.build();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
int requestAudioFocusRes = audioManager.requestAudioFocus(mAudioFocusRequest);
|
|
622
|
+
|
|
623
|
+
String requestAudioFocusResStr;
|
|
624
|
+
switch (requestAudioFocusRes) {
|
|
625
|
+
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
|
|
626
|
+
requestAudioFocusResStr = "AUDIOFOCUS_REQUEST_FAILED";
|
|
627
|
+
break;
|
|
628
|
+
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
|
|
629
|
+
isAudioFocused = true;
|
|
630
|
+
requestAudioFocusResStr = "AUDIOFOCUS_REQUEST_GRANTED";
|
|
631
|
+
break;
|
|
632
|
+
case AudioManager.AUDIOFOCUS_REQUEST_DELAYED:
|
|
633
|
+
requestAudioFocusResStr = "AUDIOFOCUS_REQUEST_DELAYED";
|
|
634
|
+
break;
|
|
635
|
+
default:
|
|
636
|
+
requestAudioFocusResStr = "AUDIOFOCUS_REQUEST_UNKNOWN";
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return requestAudioFocusResStr;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private String requestAudioFocusOld() {
|
|
644
|
+
if (isAudioFocused) {
|
|
645
|
+
return "";
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
int requestAudioFocusRes = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
|
649
|
+
|
|
650
|
+
String requestAudioFocusResStr;
|
|
651
|
+
switch (requestAudioFocusRes) {
|
|
652
|
+
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
|
|
653
|
+
requestAudioFocusResStr = "AUDIOFOCUS_REQUEST_FAILED";
|
|
654
|
+
break;
|
|
655
|
+
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
|
|
656
|
+
isAudioFocused = true;
|
|
657
|
+
requestAudioFocusResStr = "AUDIOFOCUS_REQUEST_GRANTED";
|
|
658
|
+
break;
|
|
659
|
+
default:
|
|
660
|
+
requestAudioFocusResStr = "AUDIOFOCUS_REQUEST_UNKNOWN";
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return requestAudioFocusResStr;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
@ReactMethod
|
|
668
|
+
public void abandonAudioFocusJS(Promise promise) {
|
|
669
|
+
promise.resolve(abandonAudioFocus());
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
private String abandonAudioFocus() {
|
|
673
|
+
String abandonAudioFocusResStr = (android.os.Build.VERSION.SDK_INT >= 26)
|
|
674
|
+
? abandonAudioFocusV26()
|
|
675
|
+
: abandonAudioFocusOld();
|
|
676
|
+
// Log.d(TAG, "abandonAudioFocus(): res = " + abandonAudioFocusResStr);
|
|
677
|
+
return abandonAudioFocusResStr;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private String abandonAudioFocusV26() {
|
|
681
|
+
if (!isAudioFocused || mAudioFocusRequest == null) {
|
|
682
|
+
return "";
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
int abandonAudioFocusRes = audioManager.abandonAudioFocusRequest(mAudioFocusRequest);
|
|
686
|
+
String abandonAudioFocusResStr;
|
|
687
|
+
switch (abandonAudioFocusRes) {
|
|
688
|
+
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
|
|
689
|
+
abandonAudioFocusResStr = "AUDIOFOCUS_REQUEST_FAILED";
|
|
690
|
+
break;
|
|
691
|
+
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
|
|
692
|
+
isAudioFocused = false;
|
|
693
|
+
abandonAudioFocusResStr = "AUDIOFOCUS_REQUEST_GRANTED";
|
|
694
|
+
break;
|
|
695
|
+
default:
|
|
696
|
+
abandonAudioFocusResStr = "AUDIOFOCUS_REQUEST_UNKNOWN";
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return abandonAudioFocusResStr;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
private String abandonAudioFocusOld() {
|
|
704
|
+
if (!isAudioFocused) {
|
|
705
|
+
return "";
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
int abandonAudioFocusRes = audioManager.abandonAudioFocus(this);
|
|
709
|
+
|
|
710
|
+
String abandonAudioFocusResStr;
|
|
711
|
+
switch (abandonAudioFocusRes) {
|
|
712
|
+
case AudioManager.AUDIOFOCUS_REQUEST_FAILED:
|
|
713
|
+
abandonAudioFocusResStr = "AUDIOFOCUS_REQUEST_FAILED";
|
|
714
|
+
break;
|
|
715
|
+
case AudioManager.AUDIOFOCUS_REQUEST_GRANTED:
|
|
716
|
+
isAudioFocused = false;
|
|
717
|
+
abandonAudioFocusResStr = "AUDIOFOCUS_REQUEST_GRANTED";
|
|
718
|
+
break;
|
|
719
|
+
default:
|
|
720
|
+
abandonAudioFocusResStr = "AUDIOFOCUS_REQUEST_UNKNOWN";
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return abandonAudioFocusResStr;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
@ReactMethod
|
|
728
|
+
public void pokeScreen(int timeout) {
|
|
729
|
+
// Log.d(TAG, "pokeScreen()");
|
|
730
|
+
wakeLockUtils.acquirePokeFullWakeLockReleaseAfter(timeout); // --- default 3000 ms
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private void debugScreenPowerState() {
|
|
734
|
+
String isDeviceIdleMode = "unknow"; // --- API 23
|
|
735
|
+
String isIgnoringBatteryOptimizations = "unknow"; // --- API 23
|
|
736
|
+
String isPowerSaveMode = "unknow"; // --- API 21
|
|
737
|
+
String isInteractive = "unknow"; // --- API 20 ( before since API 7 is: isScreenOn())
|
|
738
|
+
String screenState = "unknow"; // --- API 20
|
|
739
|
+
|
|
740
|
+
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
|
741
|
+
isDeviceIdleMode = String.format("%s", mPowerManager.isDeviceIdleMode());
|
|
742
|
+
isIgnoringBatteryOptimizations = String.format("%s", mPowerManager.isIgnoringBatteryOptimizations(mPackageName));
|
|
743
|
+
}
|
|
744
|
+
if (android.os.Build.VERSION.SDK_INT >= 21) {
|
|
745
|
+
isPowerSaveMode = String.format("%s", mPowerManager.isPowerSaveMode());
|
|
746
|
+
}
|
|
747
|
+
if (android.os.Build.VERSION.SDK_INT >= 20) {
|
|
748
|
+
isInteractive = String.format("%s", mPowerManager.isInteractive());
|
|
749
|
+
Display display = mWindowManager.getDefaultDisplay();
|
|
750
|
+
switch (display.getState()) {
|
|
751
|
+
case Display.STATE_OFF:
|
|
752
|
+
screenState = "STATE_OFF";
|
|
753
|
+
break;
|
|
754
|
+
case Display.STATE_ON:
|
|
755
|
+
screenState = "STATE_ON";
|
|
756
|
+
break;
|
|
757
|
+
case Display.STATE_DOZE:
|
|
758
|
+
screenState = "STATE_DOZE";
|
|
759
|
+
break;
|
|
760
|
+
case Display.STATE_DOZE_SUSPEND:
|
|
761
|
+
screenState = "STATE_DOZE_SUSPEND";
|
|
762
|
+
break;
|
|
763
|
+
default:
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
isInteractive = String.format("%s", mPowerManager.isScreenOn());
|
|
768
|
+
}
|
|
769
|
+
// Log.d(TAG, String.format("debugScreenPowerState(): screenState='%s', isInteractive='%s', isPowerSaveMode='%s', isDeviceIdleMode='%s', isIgnoringBatteryOptimizations='%s'", screenState, isInteractive, isPowerSaveMode, isDeviceIdleMode, isIgnoringBatteryOptimizations));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
@ReactMethod
|
|
773
|
+
public void turnScreenOn() {
|
|
774
|
+
if (proximityManager.isProximityWakeLockSupported()) {
|
|
775
|
+
// Log.d(TAG, "turnScreenOn(): use proximity lock.");
|
|
776
|
+
proximityManager.releaseProximityWakeLock(true);
|
|
777
|
+
} else {
|
|
778
|
+
// Log.d(TAG, "turnScreenOn(): proximity lock is not supported. try manually.");
|
|
779
|
+
manualTurnScreenOn();
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
@ReactMethod
|
|
784
|
+
public void turnScreenOff() {
|
|
785
|
+
if (proximityManager.isProximityWakeLockSupported()) {
|
|
786
|
+
// Log.d(TAG, "turnScreenOff(): use proximity lock.");
|
|
787
|
+
proximityManager.acquireProximityWakeLock();
|
|
788
|
+
} else {
|
|
789
|
+
// Log.d(TAG, "turnScreenOff(): proximity lock is not supported. try manually.");
|
|
790
|
+
manualTurnScreenOff();
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
@ReactMethod
|
|
795
|
+
public void setKeepScreenOn(final boolean enable) {
|
|
796
|
+
// Log.d(TAG, "setKeepScreenOn() " + enable);
|
|
797
|
+
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
798
|
+
public void run() {
|
|
799
|
+
Activity mCurrentActivity = getCurrentActivity();
|
|
800
|
+
if (mCurrentActivity == null) {
|
|
801
|
+
// Log.d(TAG, "ReactContext doesn't hava any Activity attached.");
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
Window window = mCurrentActivity.getWindow();
|
|
805
|
+
if (enable) {
|
|
806
|
+
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
807
|
+
} else {
|
|
808
|
+
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
@ReactMethod
|
|
815
|
+
public void setSpeakerphoneOn(final boolean enable) {
|
|
816
|
+
if (enable != audioManager.isSpeakerphoneOn()) {
|
|
817
|
+
// Log.d(TAG, "setSpeakerphoneOn(): " + enable);
|
|
818
|
+
audioManager.setSpeakerphoneOn(enable);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// --- TODO (zxcpoiu): These two api name is really confusing. should be changed.
|
|
823
|
+
/**
|
|
824
|
+
* flag: Int
|
|
825
|
+
* 0: use default action
|
|
826
|
+
* 1: force speaker on
|
|
827
|
+
* -1: force speaker off
|
|
828
|
+
*/
|
|
829
|
+
@ReactMethod
|
|
830
|
+
public void setForceSpeakerphoneOn(final int flag) {
|
|
831
|
+
if (flag < -1 || flag > 1) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
// Log.d(TAG, "setForceSpeakerphoneOn() flag: " + flag);
|
|
835
|
+
forceSpeakerOn = flag;
|
|
836
|
+
|
|
837
|
+
// --- will call updateAudioDeviceState()
|
|
838
|
+
// --- Note: in some devices, it may not contains specified route thus will not be effected.
|
|
839
|
+
if (flag == 1) {
|
|
840
|
+
selectAudioDevice(AudioDevice.SPEAKER_PHONE);
|
|
841
|
+
} else if (flag == -1) {
|
|
842
|
+
selectAudioDevice(AudioDevice.EARPIECE); // --- use the most common earpiece to force `speaker off`
|
|
843
|
+
} else {
|
|
844
|
+
selectAudioDevice(AudioDevice.NONE); // --- NONE will follow default route, the default route of `video` call is speaker.
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// --- TODO (zxcpoiu): Implement api to let user choose audio devices
|
|
849
|
+
|
|
850
|
+
@ReactMethod
|
|
851
|
+
public void setMicrophoneMute(final boolean enable) {
|
|
852
|
+
if (enable != audioManager.isMicrophoneMute()) {
|
|
853
|
+
// Log.d(TAG, "setMicrophoneMute(): " + enable);
|
|
854
|
+
audioManager.setMicrophoneMute(enable);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
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
|
+
@ReactMethod
|
|
1467
|
+
public void chooseAudioRoute(String audioRoute, Promise promise) {
|
|
1468
|
+
// Log.e(TAG, "RNInCallManager.chooseAudioRoute(): user choose audioDevice = " + audioRoute);
|
|
1469
|
+
|
|
1470
|
+
if (audioRoute.equals(AudioDevice.EARPIECE.name()) || audioRoute.equals("earpiece")) {
|
|
1471
|
+
selectAudioDevice(AudioDevice.EARPIECE);
|
|
1472
|
+
} else if (audioRoute.equals(AudioDevice.SPEAKER_PHONE.name()) || audioRoute.equals("speaker")) {
|
|
1473
|
+
selectAudioDevice(AudioDevice.SPEAKER_PHONE);
|
|
1474
|
+
} else if (audioRoute.equals(AudioDevice.WIRED_HEADSET.name()) || audioRoute.equals("wired")) {
|
|
1475
|
+
selectAudioDevice(AudioDevice.WIRED_HEADSET);
|
|
1476
|
+
} else if (audioRoute.equals(AudioDevice.BLUETOOTH.name()) || audioRoute.equals("bluetooth")) {
|
|
1477
|
+
selectAudioDevice(AudioDevice.BLUETOOTH);
|
|
1478
|
+
}
|
|
1479
|
+
promise.resolve(getAudioDeviceStatusMap());
|
|
1480
|
+
}
|
|
1481
|
+
|
|
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
|
+
private void pause() {
|
|
1491
|
+
if (audioManagerActivated) {
|
|
1492
|
+
// Log.d(TAG, "pause audioRouteManager");
|
|
1493
|
+
stopEvents();
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
private void resume() {
|
|
1498
|
+
if (audioManagerActivated) {
|
|
1499
|
+
// Log.d(TAG, "resume audioRouteManager");
|
|
1500
|
+
startEvents();
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
@Override
|
|
1505
|
+
public void onHostResume() {
|
|
1506
|
+
// Log.d(TAG, "onResume()");
|
|
1507
|
+
//resume();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
@Override
|
|
1511
|
+
public void onHostPause() {
|
|
1512
|
+
// Log.d(TAG, "onPause()");
|
|
1513
|
+
//pause();
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
@Override
|
|
1517
|
+
public void onHostDestroy() {
|
|
1518
|
+
// Log.d(TAG, "onDestroy()");
|
|
1519
|
+
stopRingtone();
|
|
1520
|
+
stopRingback();
|
|
1521
|
+
stopBusytone();
|
|
1522
|
+
stop();
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
private void updateAudioRoute() {
|
|
1526
|
+
if (!automatic) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
updateAudioDeviceState();
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// ===== NOTE: below functions is based on appRTC DEMO M64 ===== //
|
|
1533
|
+
/** Changes selection of the currently active audio device. */
|
|
1534
|
+
private void setAudioDeviceInternal(AudioDevice device) {
|
|
1535
|
+
// Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")");
|
|
1536
|
+
if (!audioDevices.contains(device)) {
|
|
1537
|
+
// Log.e(TAG, "specified audio device does not exist");
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
switch (device) {
|
|
1541
|
+
case SPEAKER_PHONE:
|
|
1542
|
+
setSpeakerphoneOn(true);
|
|
1543
|
+
audioManager.setWiredHeadsetOn(false);
|
|
1544
|
+
stopProximitySensor();
|
|
1545
|
+
break;
|
|
1546
|
+
case EARPIECE:
|
|
1547
|
+
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
1548
|
+
setSpeakerphoneOn(false);
|
|
1549
|
+
startProximitySensor();
|
|
1550
|
+
audioManager.setWiredHeadsetOn(false);
|
|
1551
|
+
break;
|
|
1552
|
+
case WIRED_HEADSET:
|
|
1553
|
+
audioManager.setWiredHeadsetOn(true);
|
|
1554
|
+
setSpeakerphoneOn(false);
|
|
1555
|
+
stopProximitySensor();
|
|
1556
|
+
break;
|
|
1557
|
+
case BLUETOOTH:
|
|
1558
|
+
setSpeakerphoneOn(false);
|
|
1559
|
+
audioManager.setWiredHeadsetOn(false);
|
|
1560
|
+
stopProximitySensor();
|
|
1561
|
+
break;
|
|
1562
|
+
default:
|
|
1563
|
+
// Log.e(TAG, "Invalid audio device selection");
|
|
1564
|
+
break;
|
|
1565
|
+
}
|
|
1566
|
+
selectedAudioDevice = device;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* Changes default audio device.
|
|
1573
|
+
* TODO(henrika): add usage of this method in the AppRTCMobile client.
|
|
1574
|
+
*/
|
|
1575
|
+
public void setDefaultAudioDevice(AudioDevice defaultDevice) {
|
|
1576
|
+
switch (defaultDevice) {
|
|
1577
|
+
case SPEAKER_PHONE:
|
|
1578
|
+
defaultAudioDevice = defaultDevice;
|
|
1579
|
+
break;
|
|
1580
|
+
case EARPIECE:
|
|
1581
|
+
if (hasEarpiece()) {
|
|
1582
|
+
defaultAudioDevice = defaultDevice;
|
|
1583
|
+
} else {
|
|
1584
|
+
defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
|
|
1585
|
+
}
|
|
1586
|
+
break;
|
|
1587
|
+
default:
|
|
1588
|
+
// Log.e(TAG, "Invalid default audio device selection");
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
// Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
|
|
1592
|
+
updateAudioDeviceState();
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/** Changes selection of the currently active audio device. */
|
|
1596
|
+
public void selectAudioDevice(AudioDevice device) {
|
|
1597
|
+
// Log.e(TAG,"Selecting "+device.name());
|
|
1598
|
+
userSelectedAudioDevice = device;
|
|
1599
|
+
updateAudioDeviceState();
|
|
1600
|
+
if (device != AudioDevice.NONE && !audioDevices.contains(device)) {
|
|
1601
|
+
// Log.e(TAG, "selectAudioDevice() Can not select " + device + " from available " + audioDevices);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
/** Returns current set of available/selectable audio devices. */
|
|
1607
|
+
public Set<AudioDevice> getAudioDevices() {
|
|
1608
|
+
return Collections.unmodifiableSet(new HashSet<>(audioDevices));
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/** Returns the currently selected audio device. */
|
|
1612
|
+
public AudioDevice getSelectedAudioDevice() {
|
|
1613
|
+
return selectedAudioDevice;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/** Helper method for receiver registration. */
|
|
1617
|
+
private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
|
|
1618
|
+
ReactContext reactContext = getReactApplicationContext();
|
|
1619
|
+
if (reactContext != null) {
|
|
1620
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
1621
|
+
reactContext.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
|
1622
|
+
} else {
|
|
1623
|
+
reactContext.registerReceiver(receiver, filter);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/** Helper method for unregistration of an existing receiver. */
|
|
1629
|
+
private void unregisterReceiver(final BroadcastReceiver receiver) {
|
|
1630
|
+
final ReactContext reactContext = this.getReactApplicationContext();
|
|
1631
|
+
if (reactContext != null) {
|
|
1632
|
+
try {
|
|
1633
|
+
reactContext.unregisterReceiver(receiver);
|
|
1634
|
+
} catch (final Exception e) {
|
|
1635
|
+
// Log.d(TAG, "unregisterReceiver() failed");
|
|
1636
|
+
}
|
|
1637
|
+
} else {
|
|
1638
|
+
// Log.d(TAG, "unregisterReceiver() reactContext is null");
|
|
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;
|
|
1659
|
+
}
|
|
1660
|
+
audioManager.setMicrophoneMute(on);
|
|
1661
|
+
}
|
|
1662
|
+
*/
|
|
1663
|
+
|
|
1664
|
+
/** Gets the current earpiece state. */
|
|
1665
|
+
private boolean hasEarpiece() {
|
|
1666
|
+
return getReactApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/**
|
|
1670
|
+
* Checks whether a wired headset is connected or not.
|
|
1671
|
+
* This is not a valid indication that audio playback is actually over
|
|
1672
|
+
* the wired headset as audio routing depends on other conditions. We
|
|
1673
|
+
* only use it as an early indicator (during initialization) of an attached
|
|
1674
|
+
* wired headset.
|
|
1675
|
+
*/
|
|
1676
|
+
@Deprecated
|
|
1677
|
+
private boolean hasWiredHeadset() {
|
|
1678
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
1679
|
+
return audioManager.isWiredHeadsetOn();
|
|
1680
|
+
} else {
|
|
1681
|
+
final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
|
|
1682
|
+
for (AudioDeviceInfo device : devices) {
|
|
1683
|
+
final int type = device.getType();
|
|
1684
|
+
if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
|
|
1685
|
+
// Log.d(TAG, "hasWiredHeadset: found wired headset");
|
|
1686
|
+
return true;
|
|
1687
|
+
} else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
|
|
1688
|
+
// Log.d(TAG, "hasWiredHeadset: found USB audio device");
|
|
1689
|
+
return true;
|
|
1690
|
+
} else if (type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
|
|
1691
|
+
// Log.d(TAG, "hasWiredHeadset: found wired headphones");
|
|
1692
|
+
return true;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
@ReactMethod
|
|
1700
|
+
public void getIsWiredHeadsetPluggedIn(Promise promise) {
|
|
1701
|
+
promise.resolve(this.hasWiredHeadset());
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
@ReactMethod
|
|
1705
|
+
public void isBluetoothHeadsetConnected(Promise promise) {
|
|
1706
|
+
UiThreadUtil.runOnUiThread(() -> {
|
|
1707
|
+
try {
|
|
1708
|
+
if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
|
|
1709
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
|
|
1710
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) {
|
|
1711
|
+
promise.resolve(true);
|
|
1712
|
+
// Log.i("Bluetooth State:", "isBluetoothHeadsetConnected: true");
|
|
1713
|
+
} else {
|
|
1714
|
+
promise.resolve(false);
|
|
1715
|
+
// Log.i("Bluetooth State:", "isBluetoothHeadsetConnected: false");
|
|
1716
|
+
}
|
|
1717
|
+
} catch (Exception e) {
|
|
1718
|
+
promise.resolve(false);
|
|
1719
|
+
Log.e("Bluetooth State:", "isBluetoothHeadsetConnected: false due to: ", e);
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
|
|
1725
|
+
/**
|
|
1726
|
+
* Updates list of possible audio devices and make new device selection.
|
|
1727
|
+
*/
|
|
1728
|
+
public void updateAudioDeviceState() {
|
|
1729
|
+
UiThreadUtil.runOnUiThread(() -> {
|
|
1730
|
+
// Log.e("Bluetooth State: ",bluetoothManager.getState().toString());
|
|
1731
|
+
if(bluetoothManager.getState().toString().equals("UNINITIALIZED"))
|
|
1732
|
+
bluetoothManager.start();
|
|
1733
|
+
hasWiredHeadset= this.hasWiredHeadset();
|
|
1734
|
+
// Log.d(TAG, "--- updateAudioDeviceState: "
|
|
1735
|
+
// + "wired headset=" + hasWiredHeadset + ", "
|
|
1736
|
+
// + "BT state=" + bluetoothManager.getState());
|
|
1737
|
+
// Log.d(TAG, "Device status: "
|
|
1738
|
+
// + "available=" + audioDevices + ", "
|
|
1739
|
+
// + "selected=" + selectedAudioDevice + ", "
|
|
1740
|
+
// + "user selected=" + userSelectedAudioDevice);
|
|
1741
|
+
|
|
1742
|
+
// Check if any Bluetooth headset is connected. The internal BT state will
|
|
1743
|
+
// change accordingly.
|
|
1744
|
+
if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
|
|
1745
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAVAILABLE
|
|
1746
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_DISCONNECTING) {
|
|
1747
|
+
bluetoothManager.updateDevice();
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// Update the set of available audio devices.
|
|
1751
|
+
Set<AudioDevice> newAudioDevices = new HashSet<>();
|
|
1752
|
+
|
|
1753
|
+
// always assume device has speaker phone
|
|
1754
|
+
newAudioDevices.add(AudioDevice.SPEAKER_PHONE);
|
|
1755
|
+
|
|
1756
|
+
if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
|
|
1757
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
|
|
1758
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) {
|
|
1759
|
+
newAudioDevices.add(AudioDevice.BLUETOOTH);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
if (hasWiredHeadset) {
|
|
1763
|
+
newAudioDevices.add(AudioDevice.WIRED_HEADSET);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
if (hasEarpiece()) {
|
|
1767
|
+
newAudioDevices.add(AudioDevice.EARPIECE);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// --- check whether user selected audio device is available
|
|
1771
|
+
if (userSelectedAudioDevice != null
|
|
1772
|
+
&& userSelectedAudioDevice != AudioDevice.NONE
|
|
1773
|
+
&& !newAudioDevices.contains(userSelectedAudioDevice)) {
|
|
1774
|
+
userSelectedAudioDevice = AudioDevice.NONE;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// Store state which is set to true if the device list has changed.
|
|
1778
|
+
boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
|
|
1779
|
+
// Update the existing audio device set.
|
|
1780
|
+
audioDevices = newAudioDevices;
|
|
1781
|
+
|
|
1782
|
+
AudioDevice newAudioDevice = getPreferredAudioDevice();
|
|
1783
|
+
|
|
1784
|
+
// --- stop bluetooth if needed
|
|
1785
|
+
if (selectedAudioDevice == AudioDevice.BLUETOOTH
|
|
1786
|
+
&& newAudioDevice != AudioDevice.BLUETOOTH
|
|
1787
|
+
&& (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
|
|
1788
|
+
|| bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING)
|
|
1789
|
+
) {
|
|
1790
|
+
bluetoothManager.stopScoAudio();
|
|
1791
|
+
bluetoothManager.updateDevice();
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// --- start bluetooth if needed
|
|
1795
|
+
if (selectedAudioDevice != AudioDevice.BLUETOOTH
|
|
1796
|
+
&& newAudioDevice == AudioDevice.BLUETOOTH
|
|
1797
|
+
&& bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) {
|
|
1798
|
+
// Attempt to start Bluetooth SCO audio (takes a few second to start).
|
|
1799
|
+
if (!bluetoothManager.startScoAudio()) {
|
|
1800
|
+
// Remove BLUETOOTH from list of available devices since SCO failed.
|
|
1801
|
+
audioDevices.remove(AudioDevice.BLUETOOTH);
|
|
1802
|
+
audioDeviceSetUpdated = true;
|
|
1803
|
+
if (userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
|
|
1804
|
+
userSelectedAudioDevice = AudioDevice.NONE;
|
|
1805
|
+
}
|
|
1806
|
+
newAudioDevice = getPreferredAudioDevice();
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
if (newAudioDevice == AudioDevice.BLUETOOTH
|
|
1811
|
+
&& bluetoothManager.getState() != AppRTCBluetoothManager.State.SCO_CONNECTED) {
|
|
1812
|
+
newAudioDevice = getPreferredAudioDevice(true); // --- skip bluetooth
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// Switch to new device but only if there has been any changes.
|
|
1816
|
+
if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
|
|
1817
|
+
|
|
1818
|
+
// Do the required device switch.
|
|
1819
|
+
setAudioDeviceInternal(newAudioDevice);
|
|
1820
|
+
// Log.d(TAG, "New device status: "
|
|
1821
|
+
// + "available=" + audioDevices + ", "
|
|
1822
|
+
// + "selected=" + newAudioDevice);
|
|
1823
|
+
/*
|
|
1824
|
+
if (audioManagerEvents != null) {
|
|
1825
|
+
// Notify a listening client that audio device has been changed.
|
|
1826
|
+
audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices);
|
|
1827
|
+
}
|
|
1828
|
+
*/
|
|
1829
|
+
sendEvent("onAudioDeviceChanged", getAudioDeviceStatusMap());
|
|
1830
|
+
}
|
|
1831
|
+
// Log.d(TAG, "--- updateAudioDeviceState done");
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
private WritableMap getAudioDeviceStatusMap() {
|
|
1836
|
+
WritableMap data = Arguments.createMap();
|
|
1837
|
+
String audioDevicesJson = "[";
|
|
1838
|
+
for (AudioDevice s: audioDevices) {
|
|
1839
|
+
audioDevicesJson += s.name() + ",";
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// --- strip the last `,`
|
|
1843
|
+
if (audioDevicesJson.length() > 1) {
|
|
1844
|
+
audioDevicesJson = audioDevicesJson.substring(0, audioDevicesJson.length() - 1);
|
|
1845
|
+
}
|
|
1846
|
+
audioDevicesJson += "]";
|
|
1847
|
+
|
|
1848
|
+
data.putString("availableAudioDeviceList", audioDevicesJson);
|
|
1849
|
+
data.putString("selectedAudioDevice", (selectedAudioDevice == null) ? "" : selectedAudioDevice.name());
|
|
1850
|
+
|
|
1851
|
+
return data;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
private AudioDevice getPreferredAudioDevice() {
|
|
1855
|
+
return getPreferredAudioDevice(false);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
private AudioDevice getPreferredAudioDevice(boolean skipBluetooth) {
|
|
1859
|
+
final AudioDevice newAudioDevice;
|
|
1860
|
+
|
|
1861
|
+
if (userSelectedAudioDevice != null && userSelectedAudioDevice != AudioDevice.NONE) {
|
|
1862
|
+
newAudioDevice = userSelectedAudioDevice;
|
|
1863
|
+
} else if (!skipBluetooth && audioDevices.contains(AudioDevice.BLUETOOTH)) {
|
|
1864
|
+
// If a Bluetooth is connected, then it should be used as output audio
|
|
1865
|
+
// device. Note that it is not sufficient that a headset is available;
|
|
1866
|
+
// an active SCO channel must also be up and running.
|
|
1867
|
+
newAudioDevice = AudioDevice.BLUETOOTH;
|
|
1868
|
+
} else if (audioDevices.contains(AudioDevice.WIRED_HEADSET)) {
|
|
1869
|
+
// If a wired headset is connected, but Bluetooth is not, then wired headset is used as
|
|
1870
|
+
// audio device.
|
|
1871
|
+
newAudioDevice = AudioDevice.WIRED_HEADSET;
|
|
1872
|
+
} else if (audioDevices.contains(defaultAudioDevice)) {
|
|
1873
|
+
newAudioDevice = defaultAudioDevice;
|
|
1874
|
+
} else {
|
|
1875
|
+
newAudioDevice = AudioDevice.SPEAKER_PHONE;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
return newAudioDevice;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|