omikit-plugin 3.3.29 → 4.0.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/README.md +994 -1217
- package/android/build.gradle +22 -72
- package/android/gradle.properties +4 -4
- package/android/src/main/java/com/omikitplugin/FLLocalCameraModule.kt +1 -1
- package/android/src/main/java/com/omikitplugin/FLRemoteCameraModule.kt +1 -1
- package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +326 -356
- package/android/src/main/java/com/omikitplugin/constants/constant.kt +2 -1
- package/ios/CallProcess/CallManager.swift +45 -35
- package/ios/Constant/Constant.swift +1 -0
- package/ios/Library/OmikitPlugin.m +75 -1
- package/ios/Library/OmikitPlugin.swift +199 -16
- package/ios/OmikitPlugin-Protocol.h +161 -0
- package/lib/commonjs/NativeOmikitPlugin.js +9 -0
- package/lib/commonjs/NativeOmikitPlugin.js.map +1 -0
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/omi_audio_type.js +20 -0
- package/lib/commonjs/omi_audio_type.js.map +1 -0
- package/lib/commonjs/omi_local_camera.js +18 -2
- package/lib/commonjs/omi_local_camera.js.map +1 -1
- package/lib/commonjs/omi_remote_camera.js +18 -2
- package/lib/commonjs/omi_remote_camera.js.map +1 -1
- package/lib/commonjs/omi_start_call_status.js +30 -0
- package/lib/commonjs/omi_start_call_status.js.map +1 -1
- package/lib/commonjs/omikit.js +125 -14
- package/lib/commonjs/omikit.js.map +1 -1
- package/lib/module/NativeOmikitPlugin.js +3 -0
- package/lib/module/NativeOmikitPlugin.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/omi_audio_type.js +14 -0
- package/lib/module/omi_audio_type.js.map +1 -0
- package/lib/module/omi_local_camera.js +19 -2
- package/lib/module/omi_local_camera.js.map +1 -1
- package/lib/module/omi_remote_camera.js +19 -2
- package/lib/module/omi_remote_camera.js.map +1 -1
- package/lib/module/omi_start_call_status.js +30 -0
- package/lib/module/omi_start_call_status.js.map +1 -1
- package/lib/module/omikit.js +119 -15
- package/lib/module/omikit.js.map +1 -1
- package/omikit-plugin.podspec +26 -24
- package/package.json +11 -2
- package/src/NativeOmikitPlugin.ts +160 -0
- package/src/index.tsx +2 -1
- package/src/omi_audio_type.tsx +9 -0
- package/src/omi_local_camera.tsx +17 -3
- package/src/omi_remote_camera.tsx +17 -3
- package/src/omi_start_call_status.tsx +29 -10
- package/src/omikit.tsx +118 -28
- package/src/types/index.d.ts +111 -11
|
@@ -120,7 +120,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
120
120
|
private val mainScope = CoroutineScope(Dispatchers.Main)
|
|
121
121
|
private var isIncoming: Boolean = false
|
|
122
122
|
private var isAnswerCall: Boolean = false
|
|
123
|
-
private var permissionPromise: Promise? = null
|
|
123
|
+
@Volatile private var permissionPromise: Promise? = null
|
|
124
124
|
|
|
125
125
|
// Call state management to prevent concurrent calls
|
|
126
126
|
private var isCallInProgress: Boolean = false
|
|
@@ -134,7 +134,22 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
134
134
|
override fun getName(): String {
|
|
135
135
|
return NAME
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
|
|
138
|
+
override fun getConstants(): MutableMap<String, Any> {
|
|
139
|
+
return mutableMapOf(
|
|
140
|
+
"CALL_STATE_CHANGED" to CALL_STATE_CHANGED,
|
|
141
|
+
"MUTED" to MUTED,
|
|
142
|
+
"HOLD" to HOLD,
|
|
143
|
+
"SPEAKER" to SPEAKER,
|
|
144
|
+
"REMOTE_VIDEO_READY" to REMOTE_VIDEO_READY,
|
|
145
|
+
"CLICK_MISSED_CALL" to CLICK_MISSED_CALL,
|
|
146
|
+
"SWITCHBOARD_ANSWER" to SWITCHBOARD_ANSWER,
|
|
147
|
+
"CALL_QUALITY" to CALL_QUALITY,
|
|
148
|
+
"AUDIO_CHANGE" to AUDIO_CHANGE,
|
|
149
|
+
"REQUEST_PERMISSION" to REQUEST_PERMISSION
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
138
153
|
/**
|
|
139
154
|
* Check if we can start a new call (no concurrent calls, cooldown passed)
|
|
140
155
|
*/
|
|
@@ -145,12 +160,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
145
160
|
|
|
146
161
|
// Check if call is in progress or cooldown not passed
|
|
147
162
|
if (isCallInProgress) {
|
|
148
|
-
|
|
163
|
+
|
|
149
164
|
return false
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
if (timeSinceLastCall < callCooldownMs) {
|
|
153
|
-
Log.d("OMISDK", "🚫 Call blocked: Cooldown period (${callCooldownMs - timeSinceLastCall}ms remaining)")
|
|
154
168
|
return false
|
|
155
169
|
}
|
|
156
170
|
|
|
@@ -165,7 +179,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
165
179
|
synchronized(callStateLock) {
|
|
166
180
|
isCallInProgress = true
|
|
167
181
|
lastCallTime = System.currentTimeMillis()
|
|
168
|
-
Log.d("OMISDK", "📞 Call started, marking in progress")
|
|
169
182
|
}
|
|
170
183
|
}
|
|
171
184
|
|
|
@@ -175,7 +188,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
175
188
|
private fun markCallEnded() {
|
|
176
189
|
synchronized(callStateLock) {
|
|
177
190
|
isCallInProgress = false
|
|
178
|
-
Log.d("OMISDK", "📴 Call ended, clearing in progress flag")
|
|
179
191
|
}
|
|
180
192
|
}
|
|
181
193
|
|
|
@@ -183,11 +195,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
183
195
|
private val handler = Handler(Looper.getMainLooper())
|
|
184
196
|
|
|
185
197
|
override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {
|
|
186
|
-
Log.d("OMISDK", "=>> incomingReceived CALLED - BEFORE: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
|
|
187
198
|
isIncoming = true;
|
|
188
199
|
isAnswerCall = false; // Reset answer state for new incoming call
|
|
189
|
-
Log.d("OMISDK", "=>> incomingReceived AFTER SET - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall, phoneNumber: $phoneNumber")
|
|
190
|
-
|
|
191
200
|
val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
|
|
192
201
|
|
|
193
202
|
val map: WritableMap = WritableNativeMap().apply {
|
|
@@ -210,11 +219,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
210
219
|
transactionId: String?,
|
|
211
220
|
) {
|
|
212
221
|
isAnswerCall = true
|
|
213
|
-
Log.d("OMISDK", "=>> ON CALL ESTABLISHED => ")
|
|
214
222
|
|
|
215
223
|
Handler(Looper.getMainLooper()).postDelayed({
|
|
216
|
-
Log.d("OmikitReactNative", "onCallEstablished")
|
|
217
|
-
|
|
218
224
|
val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
|
|
219
225
|
|
|
220
226
|
// ✅ Sử dụng safe WritableMap creation
|
|
@@ -233,18 +239,13 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
override fun onCallEnd(callInfo: MutableMap<String, Any?>, statusCode: Int) {
|
|
236
|
-
Log.d("OMISDK RN", "=>> onCallEnd CALLED - BEFORE RESET: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
|
|
237
|
-
Log.d("OMISDK RN", "=>> onCallEnd callInfo => $callInfo")
|
|
238
|
-
|
|
239
242
|
// Reset call state variables
|
|
240
243
|
isIncoming = false
|
|
241
244
|
isAnswerCall = false
|
|
242
245
|
// Clear call progress state when remote party ends call
|
|
243
246
|
markCallEnded()
|
|
244
|
-
Log.d("OMISDK", "=>> onCallEnd AFTER RESET - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
|
|
245
|
-
|
|
246
247
|
// Kiểm tra kiểu dữ liệu trước khi ép kiểu để tránh lỗi
|
|
247
|
-
val call = callInfo
|
|
248
|
+
val call = callInfo
|
|
248
249
|
|
|
249
250
|
val timeStartToAnswer = (call["time_start_to_answer"] as? Long) ?: 0L
|
|
250
251
|
val timeEnd = (call["time_end"] as? Long) ?: 0L
|
|
@@ -267,18 +268,14 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
267
268
|
)
|
|
268
269
|
|
|
269
270
|
val map = createSafeWritableMap(eventData)
|
|
270
|
-
|
|
271
|
-
Log.d("OMISDK RN", "=>> onCallEnd => ")
|
|
272
271
|
sendEvent(CALL_STATE_CHANGED, map)
|
|
273
272
|
}
|
|
274
273
|
|
|
275
274
|
override fun onConnecting() {
|
|
276
|
-
Log.d("OMISDK", "=>> ON CONNECTING CALL => ")
|
|
277
|
-
|
|
278
275
|
val map: WritableMap = WritableNativeMap().apply {
|
|
279
276
|
putString("callerNumber", "")
|
|
280
277
|
putBoolean("isVideo", NotificationService.isVideo)
|
|
281
|
-
putBoolean("incoming", isIncoming
|
|
278
|
+
putBoolean("incoming", isIncoming)
|
|
282
279
|
putString("transactionId", "")
|
|
283
280
|
putString("_id", "")
|
|
284
281
|
putInt("status", CallState.connecting.value)
|
|
@@ -299,18 +296,12 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
299
296
|
val prePhoneNumber = OmiClient.prePhoneNumber ?: ""
|
|
300
297
|
val typeNumber = OmiKitUtils().checkTypeNumber(prePhoneNumber)
|
|
301
298
|
|
|
302
|
-
Log.d("OMISDK", "=>> onRinging CALLED - BEFORE: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall, callDirection: $callDirection")
|
|
303
|
-
|
|
304
299
|
if (callDirection == "inbound") {
|
|
305
300
|
isIncoming = true;
|
|
306
|
-
Log.d("OMISDK", "=>> onRinging SET isIncoming = true for inbound call")
|
|
307
301
|
} else if (callDirection == "outbound") {
|
|
308
302
|
isIncoming = false;
|
|
309
|
-
Log.d("OMISDK", "=>> onRinging SET isIncoming = false for outbound call")
|
|
310
303
|
}
|
|
311
304
|
|
|
312
|
-
Log.d("OMISDK", "=>> onRinging AFTER: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
|
|
313
|
-
|
|
314
305
|
// ✅ Sử dụng safe WritableMap creation
|
|
315
306
|
val eventData = mapOf(
|
|
316
307
|
"callerNumber" to if (callDirection == "inbound") prePhoneNumber else "",
|
|
@@ -322,8 +313,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
322
313
|
)
|
|
323
314
|
|
|
324
315
|
val map = createSafeWritableMap(eventData)
|
|
325
|
-
|
|
326
|
-
Log.d("OMISDK", if (callDirection == "inbound") "=>> ON INCOMING CALL => " else "=>> ON RINGING CALL => ")
|
|
327
316
|
sendEvent(CALL_STATE_CHANGED, map)
|
|
328
317
|
}
|
|
329
318
|
|
|
@@ -331,13 +320,21 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
331
320
|
override fun networkHealth(stat: Map<String, *>, quality: Int) {
|
|
332
321
|
val map: WritableMap = WritableNativeMap()
|
|
333
322
|
map.putInt("quality", quality)
|
|
323
|
+
// Pass full diagnostics from stat map
|
|
324
|
+
val statMap: WritableMap = WritableNativeMap()
|
|
325
|
+
(stat["mos"] as? Number)?.let { statMap.putDouble("mos", it.toDouble()) }
|
|
326
|
+
(stat["jitter"] as? Number)?.let { statMap.putDouble("jitter", it.toDouble()) }
|
|
327
|
+
(stat["latency"] as? Number)?.let { statMap.putDouble("latency", it.toDouble()) }
|
|
328
|
+
(stat["ppl"] as? Number)?.let { statMap.putDouble("packetLoss", it.toDouble()) }
|
|
329
|
+
(stat["lcn"] as? Number)?.let { statMap.putInt("lcn", it.toInt()) }
|
|
330
|
+
map.putMap("stat", statMap)
|
|
334
331
|
sendEvent(CALL_QUALITY, map)
|
|
335
332
|
}
|
|
336
333
|
|
|
337
334
|
override fun onAudioChanged(audioInfo: Map<String, Any>) {
|
|
338
335
|
val audio: WritableMap = WritableNativeMap()
|
|
339
|
-
audio.putString("name", audioInfo["name"] as String)
|
|
340
|
-
audio.putInt("type", audioInfo["type"] as Int)
|
|
336
|
+
audio.putString("name", audioInfo["name"] as? String ?: "")
|
|
337
|
+
audio.putInt("type", audioInfo["type"] as? Int ?: 0)
|
|
341
338
|
val map: WritableMap = WritableNativeMap()
|
|
342
339
|
val writeList = WritableNativeArray()
|
|
343
340
|
writeList.pushMap(audio)
|
|
@@ -355,13 +352,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
355
352
|
}
|
|
356
353
|
|
|
357
354
|
override fun onOutgoingStarted(callerId: Int, phoneNumber: String?, isVideo: Boolean?) {
|
|
358
|
-
Log.d("OMISDK", "=>> onOutgoingStarted CALLED - BEFORE: isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
|
|
359
|
-
|
|
360
355
|
// For outgoing calls, set states appropriately
|
|
361
356
|
isIncoming = false;
|
|
362
357
|
isAnswerCall = false;
|
|
363
|
-
Log.d("OMISDK", "=>> onOutgoingStarted AFTER SET - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
|
|
364
|
-
|
|
365
358
|
val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
|
|
366
359
|
|
|
367
360
|
val map: WritableMap = WritableNativeMap().apply {
|
|
@@ -384,8 +377,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
384
377
|
}
|
|
385
378
|
|
|
386
379
|
override fun onRegisterCompleted(statusCode: Int) {
|
|
387
|
-
Log.d("OMISDK", "=> ON REGISTER COMPLETED => status code: $statusCode")
|
|
388
|
-
|
|
389
380
|
if (statusCode != 200) {
|
|
390
381
|
val normalizedStatusCode = if (statusCode == 403) 853 else statusCode
|
|
391
382
|
val typeNumber = ""
|
|
@@ -413,7 +404,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
413
404
|
}
|
|
414
405
|
})
|
|
415
406
|
}
|
|
416
|
-
Log.d("OMISDK", "=>> onRequestPermission => $map")
|
|
417
407
|
sendEvent(REQUEST_PERMISSION, map)
|
|
418
408
|
|
|
419
409
|
}
|
|
@@ -456,16 +446,14 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
456
446
|
|
|
457
447
|
override fun initialize() {
|
|
458
448
|
super.initialize()
|
|
459
|
-
|
|
460
|
-
|
|
449
|
+
|
|
450
|
+
moduleInstance = this
|
|
461
451
|
reactApplicationContext!!.addActivityEventListener(this)
|
|
462
452
|
Handler(Looper.getMainLooper()).post {
|
|
463
|
-
OmiClient.getInstance(reactApplicationContext!!)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
OmiClient.getInstance(reactApplicationContext!!).setDebug(false)
|
|
453
|
+
val client = OmiClient.getInstance(reactApplicationContext!!)
|
|
454
|
+
client.addCallStateListener(this)
|
|
455
|
+
client.addCallStateListener(autoUnregisterListener)
|
|
456
|
+
client.setDebug(false)
|
|
469
457
|
}
|
|
470
458
|
}
|
|
471
459
|
|
|
@@ -482,7 +470,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
482
470
|
OmiClient.getInstance(reactApplicationContext!!).setDebug(false)
|
|
483
471
|
promise.resolve(true)
|
|
484
472
|
} catch (e: Exception) {
|
|
485
|
-
Log.e("OmikitPlugin", "❌ Error in startServices: ${e.message}", e)
|
|
486
473
|
promise.resolve(false)
|
|
487
474
|
}
|
|
488
475
|
}
|
|
@@ -493,7 +480,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
493
480
|
// ✅ Method để check status AUTO-UNREGISTER (DEPRECATED)
|
|
494
481
|
@ReactMethod
|
|
495
482
|
fun getAutoUnregisterStatus(promise: Promise) {
|
|
496
|
-
|
|
483
|
+
|
|
497
484
|
try {
|
|
498
485
|
OmiClient.getInstance(reactApplicationContext!!).getAutoUnregisterStatus { isScheduled, timeUntilExecution ->
|
|
499
486
|
try {
|
|
@@ -504,12 +491,10 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
504
491
|
)
|
|
505
492
|
promise.resolve(Arguments.makeNativeMap(status))
|
|
506
493
|
} catch (e: Exception) {
|
|
507
|
-
Log.e("OmikitPlugin", "❌ Error in getAutoUnregisterStatus callback: ${e.message}", e)
|
|
508
494
|
promise.resolve(null)
|
|
509
495
|
}
|
|
510
496
|
}
|
|
511
497
|
} catch (e: Exception) {
|
|
512
|
-
Log.e("OmikitPlugin", "❌ Error calling getAutoUnregisterStatus: ${e.message}", e)
|
|
513
498
|
promise.resolve(null)
|
|
514
499
|
}
|
|
515
500
|
}
|
|
@@ -517,7 +502,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
517
502
|
// ✅ Method để manually prevent AUTO-UNREGISTER (DEPRECATED)
|
|
518
503
|
@ReactMethod
|
|
519
504
|
fun preventAutoUnregister(reason: String, promise: Promise) {
|
|
520
|
-
|
|
505
|
+
|
|
521
506
|
// Function removed - no longer supported
|
|
522
507
|
promise.resolve(false)
|
|
523
508
|
}
|
|
@@ -525,47 +510,37 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
525
510
|
// ✅ Convenience methods cho các scenario phổ biến (DEPRECATED)
|
|
526
511
|
@ReactMethod
|
|
527
512
|
fun prepareForIncomingCall(promise: Promise) {
|
|
528
|
-
Log.w("OmikitPlugin", "⚠️ DEPRECATED: prepareForIncomingCall() - Use Silent Registration API instead")
|
|
529
513
|
try {
|
|
530
514
|
OmiClient.getInstance(reactApplicationContext!!).prepareForIncomingCall()
|
|
531
515
|
promise.resolve(true)
|
|
532
516
|
} catch (e: Exception) {
|
|
533
|
-
Log.e("OmikitPlugin", "❌ Prepare for incoming call failed: ${e.message}", e)
|
|
534
517
|
promise.resolve(false)
|
|
535
518
|
}
|
|
536
519
|
}
|
|
537
520
|
|
|
538
521
|
@ReactMethod
|
|
539
522
|
fun prepareForOutgoingCall(promise: Promise) {
|
|
540
|
-
Log.w("OmikitPlugin", "⚠️ DEPRECATED: prepareForOutgoingCall() - Use Silent Registration API instead")
|
|
541
523
|
try {
|
|
542
524
|
OmiClient.getInstance(reactApplicationContext!!).prepareForOutgoingCall()
|
|
543
525
|
promise.resolve(true)
|
|
544
526
|
} catch (e: Exception) {
|
|
545
|
-
Log.e("OmikitPlugin", "❌ Prepare for outgoing call failed: ${e.message}", e)
|
|
546
527
|
promise.resolve(false)
|
|
547
528
|
}
|
|
548
529
|
}
|
|
549
530
|
|
|
550
531
|
private fun prepareAudioSystem() {
|
|
551
532
|
try {
|
|
552
|
-
// ✅ Check network connectivity first
|
|
553
533
|
if (!isNetworkAvailable()) {
|
|
554
534
|
return
|
|
555
535
|
}
|
|
556
|
-
|
|
536
|
+
|
|
557
537
|
// Release any existing audio focus
|
|
558
538
|
val audioManager = reactApplicationContext?.getSystemService(android.content.Context.AUDIO_SERVICE) as? android.media.AudioManager
|
|
559
539
|
audioManager?.let {
|
|
560
|
-
// Reset audio mode
|
|
561
540
|
it.mode = android.media.AudioManager.MODE_NORMAL
|
|
562
541
|
}
|
|
563
|
-
|
|
564
|
-
// Small delay để audio system ổn định
|
|
565
|
-
Thread.sleep(200)
|
|
566
|
-
|
|
567
542
|
} catch (e: Exception) {
|
|
568
|
-
Log.w("OmikitPlugin", "
|
|
543
|
+
Log.w("OmikitPlugin", "Audio preparation warning: ${e.message}")
|
|
569
544
|
}
|
|
570
545
|
}
|
|
571
546
|
|
|
@@ -573,11 +548,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
573
548
|
private fun isNetworkAvailable(): Boolean {
|
|
574
549
|
return try {
|
|
575
550
|
val connectivityManager = reactApplicationContext?.getSystemService(android.content.Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
551
|
+
?: return true
|
|
552
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
553
|
+
val network = connectivityManager.activeNetwork ?: return false
|
|
554
|
+
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
|
555
|
+
capabilities.hasCapability(android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
556
|
+
} else {
|
|
557
|
+
@Suppress("DEPRECATION")
|
|
558
|
+
connectivityManager.activeNetworkInfo?.isConnectedOrConnecting == true
|
|
559
|
+
}
|
|
579
560
|
} catch (e: Exception) {
|
|
580
|
-
Log.w("OmikitPlugin", "
|
|
561
|
+
Log.w("OmikitPlugin", "Network check failed: ${e.message}")
|
|
581
562
|
true // Assume network is available if check fails
|
|
582
563
|
}
|
|
583
564
|
}
|
|
@@ -614,22 +595,32 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
614
595
|
return map
|
|
615
596
|
}
|
|
616
597
|
|
|
617
|
-
@RequiresApi(Build.VERSION_CODES.M)
|
|
618
598
|
@ReactMethod
|
|
619
599
|
fun systemAlertWindow(promise: Promise) {
|
|
600
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
601
|
+
promise.resolve(true)
|
|
602
|
+
return
|
|
603
|
+
}
|
|
620
604
|
val result = Settings.canDrawOverlays(reactApplicationContext)
|
|
621
605
|
promise.resolve(result)
|
|
622
606
|
}
|
|
623
607
|
|
|
624
|
-
@RequiresApi(Build.VERSION_CODES.M)
|
|
625
608
|
@ReactMethod
|
|
626
609
|
fun openSystemAlertSetting(promise: Promise) {
|
|
610
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
611
|
+
promise.resolve(true)
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
val ctx = reactApplicationContext ?: run {
|
|
615
|
+
promise.resolve(false)
|
|
616
|
+
return
|
|
617
|
+
}
|
|
627
618
|
val intent = Intent(
|
|
628
619
|
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
|
629
|
-
Uri.parse("package:" +
|
|
620
|
+
Uri.parse("package:" + ctx.packageName)
|
|
630
621
|
)
|
|
631
622
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
632
|
-
|
|
623
|
+
ctx.startActivity(intent)
|
|
633
624
|
promise.resolve(true)
|
|
634
625
|
}
|
|
635
626
|
|
|
@@ -657,7 +648,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
657
648
|
val audioNotificationDescription = data.getString("audioNotificationDescription") ?: "Cuộc gọi audio"
|
|
658
649
|
val videoNotificationDescription = data.getString("videoNotificationDescription") ?: "Cuộc gọi video"
|
|
659
650
|
val representName = data.getString("representName") ?: ""
|
|
660
|
-
val isUserBusy = data.getBoolean("isUserBusy")
|
|
651
|
+
val isUserBusy = if (data.hasKey("isUserBusy")) data.getBoolean("isUserBusy") else false
|
|
661
652
|
|
|
662
653
|
// Configure push notification with extracted parameters
|
|
663
654
|
OmiClient.getInstance(context).configPushNotification(
|
|
@@ -681,15 +672,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
681
672
|
|
|
682
673
|
// Configure decline call behavior
|
|
683
674
|
OmiClient.getInstance(context).configureDeclineCallBehavior(isUserBusy)
|
|
684
|
-
|
|
685
|
-
Log.d("OmikitPlugin", "✅ Push notification configured successfully")
|
|
686
675
|
promise.resolve(true)
|
|
687
676
|
} catch (e: Exception) {
|
|
688
|
-
Log.e("OmikitPlugin", "❌ Error configuring push notification: ${e.message}", e)
|
|
689
677
|
promise.reject("E_CONFIG_FAILED", "Failed to configure push notification", e)
|
|
690
678
|
}
|
|
691
679
|
} ?: run {
|
|
692
|
-
Log.e("OmikitPlugin", "❌ Current activity is null")
|
|
693
680
|
promise.reject("E_NULL_ACTIVITY", "Current activity is null")
|
|
694
681
|
}
|
|
695
682
|
} catch (e: Exception) {
|
|
@@ -704,32 +691,34 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
704
691
|
val userName = data.getString("userName")
|
|
705
692
|
val password = data.getString("password")
|
|
706
693
|
val realm = data.getString("realm")
|
|
707
|
-
val host = data.getString("host")
|
|
708
|
-
val isVideo = data.getBoolean("isVideo")
|
|
694
|
+
val host = data.getString("host").let { if (it.isNullOrEmpty()) "vh.omicrm.com" else it }
|
|
695
|
+
val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
|
|
709
696
|
val firebaseToken = data.getString("fcmToken")
|
|
710
697
|
val projectId = data.getString("projectId") ?: ""
|
|
698
|
+
val isSkipDevices = if (data.hasKey("isSkipDevices")) data.getBoolean("isSkipDevices") else false
|
|
711
699
|
|
|
712
700
|
// Validate required parameters
|
|
713
701
|
if (!ValidationHelper.validateRequired(mapOf(
|
|
714
702
|
"userName" to userName,
|
|
715
|
-
"password" to password,
|
|
703
|
+
"password" to password,
|
|
716
704
|
"realm" to realm,
|
|
717
705
|
"fcmToken" to firebaseToken
|
|
718
706
|
), promise)) return@launch
|
|
719
707
|
|
|
720
708
|
withContext(Dispatchers.Default) {
|
|
721
709
|
try {
|
|
722
|
-
//
|
|
710
|
+
// Cleanup before register using logout callback
|
|
723
711
|
try {
|
|
724
|
-
|
|
725
|
-
|
|
712
|
+
val logoutComplete = kotlinx.coroutines.CompletableDeferred<Unit>()
|
|
713
|
+
OmiClient.getInstance(reactApplicationContext!!).logout {
|
|
714
|
+
logoutComplete.complete(Unit)
|
|
715
|
+
}
|
|
716
|
+
// Wait for logout callback with timeout
|
|
717
|
+
kotlinx.coroutines.withTimeoutOrNull(3000) { logoutComplete.await() }
|
|
726
718
|
} catch (e: Exception) {
|
|
727
|
-
Log.w("OmikitPlugin", "
|
|
719
|
+
Log.w("OmikitPlugin", "Cleanup warning (expected): ${e.message}")
|
|
728
720
|
}
|
|
729
|
-
|
|
730
|
-
// ✅ Sử dụng Silent Registration API mới từ OmiSDK 2.3.67
|
|
731
|
-
Log.d("OmikitPlugin", "🔇 Using Silent Registration API for user: $userName")
|
|
732
|
-
|
|
721
|
+
|
|
733
722
|
OmiClient.getInstance(reactApplicationContext!!).silentRegister(
|
|
734
723
|
userName = userName ?: "",
|
|
735
724
|
password = password ?: "",
|
|
@@ -737,26 +726,18 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
737
726
|
isVideo = isVideo ?: true,
|
|
738
727
|
firebaseToken = firebaseToken ?: "",
|
|
739
728
|
host = host,
|
|
740
|
-
projectId = projectId
|
|
729
|
+
projectId = projectId,
|
|
730
|
+
isSkipDevices = isSkipDevices
|
|
741
731
|
) { success, statusCode, message ->
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
Log.d("OmikitPlugin", "✅ Silent registration successful - no notification, no auto-unregister")
|
|
745
|
-
// ✅ Resolve promise với kết quả từ callback
|
|
746
|
-
promise.resolve(success)
|
|
732
|
+
if (success || statusCode == 200) {
|
|
733
|
+
promise.resolve(true)
|
|
747
734
|
} else {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
promise.resolve(true)
|
|
751
|
-
} else {
|
|
752
|
-
val (errorCode, errorMessage) = OmiRegistrationStatus.getError(statusCode)
|
|
753
|
-
promise.reject(errorCode, "$errorMessage (Status: $statusCode)")
|
|
754
|
-
}
|
|
735
|
+
val (errorCode, errorMessage) = OmiRegistrationStatus.getError(statusCode)
|
|
736
|
+
promise.reject(errorCode, "$errorMessage (Status: $statusCode)")
|
|
755
737
|
}
|
|
756
738
|
}
|
|
757
739
|
|
|
758
740
|
} catch (e: Exception) {
|
|
759
|
-
Log.e("OmikitPlugin", "❌ Error during silent registration: ${e.message}", e)
|
|
760
741
|
promise.reject("ERROR_INITIALIZATION_EXCEPTION", "OMICALL initialization failed due to an unexpected error: ${e.message}. Please check your network connection and configuration.", e)
|
|
761
742
|
}
|
|
762
743
|
}
|
|
@@ -765,22 +746,18 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
765
746
|
|
|
766
747
|
@ReactMethod
|
|
767
748
|
fun initCallWithApiKey(data: ReadableMap, promise: Promise) {
|
|
768
|
-
Log.d("OmikitPlugin", "🔑 initCallWithApiKey called")
|
|
769
749
|
mainScope.launch {
|
|
770
750
|
var loginResult = false
|
|
771
751
|
val usrName = data.getString("fullName") ?: ""
|
|
772
752
|
val usrUuid = data.getString("usrUuid") ?: ""
|
|
773
753
|
val apiKey = data.getString("apiKey") ?: ""
|
|
774
|
-
val isVideo = data.getBoolean("isVideo")
|
|
754
|
+
val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
|
|
775
755
|
val phone = data.getString("phone")
|
|
776
756
|
val firebaseToken = data.getString("fcmToken") ?: ""
|
|
777
757
|
val projectId = data.getString("projectId") ?: ""
|
|
778
758
|
|
|
779
|
-
Log.d("OmikitPlugin", "🔑 Parameters - usrName: $usrName, usrUuid: $usrUuid, isVideo: $isVideo")
|
|
780
|
-
|
|
781
759
|
withContext(Dispatchers.Default) {
|
|
782
760
|
try {
|
|
783
|
-
Log.d("OmikitPlugin", "🔑 Starting validation")
|
|
784
761
|
// Validate required parameters
|
|
785
762
|
if (!ValidationHelper.validateRequired(mapOf(
|
|
786
763
|
"fullName" to usrName,
|
|
@@ -792,8 +769,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
792
769
|
return@withContext
|
|
793
770
|
}
|
|
794
771
|
|
|
795
|
-
Log.d("OmikitPlugin", "✅ Validation passed")
|
|
796
|
-
|
|
797
772
|
// Check RECORD_AUDIO permission for Android 14+
|
|
798
773
|
val hasRecordAudio = ContextCompat.checkSelfPermission(
|
|
799
774
|
reactApplicationContext,
|
|
@@ -801,28 +776,22 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
801
776
|
) == PackageManager.PERMISSION_GRANTED
|
|
802
777
|
|
|
803
778
|
if (!hasRecordAudio) {
|
|
804
|
-
Log.e("OmikitPlugin", "❌ RECORD_AUDIO permission is required for Android 14+")
|
|
805
779
|
promise.resolve(false)
|
|
806
780
|
return@withContext
|
|
807
781
|
}
|
|
808
|
-
|
|
809
|
-
Log.d("OmikitPlugin", "✅ RECORD_AUDIO permission granted")
|
|
810
|
-
|
|
811
|
-
// ✅ Cleanup trước khi register với mutex
|
|
782
|
+
// Cleanup before register using logout callback
|
|
812
783
|
try {
|
|
813
|
-
Log.d("OmikitPlugin", "🧹 Starting cleanup")
|
|
814
784
|
omiClientMutex.withLock {
|
|
815
|
-
|
|
785
|
+
val logoutComplete = kotlinx.coroutines.CompletableDeferred<Unit>()
|
|
786
|
+
OmiClient.getInstance(reactApplicationContext!!).logout {
|
|
787
|
+
logoutComplete.complete(Unit)
|
|
788
|
+
}
|
|
789
|
+
kotlinx.coroutines.withTimeoutOrNull(3000) { logoutComplete.await() }
|
|
816
790
|
}
|
|
817
|
-
delay(1000) // Chờ cleanup hoàn tất
|
|
818
|
-
Log.d("OmikitPlugin", "✅ Cleanup completed")
|
|
819
791
|
} catch (e: Exception) {
|
|
820
|
-
Log.w("OmikitPlugin", "
|
|
792
|
+
Log.w("OmikitPlugin", "Cleanup warning (expected): ${e.message}")
|
|
821
793
|
}
|
|
822
794
|
|
|
823
|
-
Log.d("OmikitPlugin", "🔑 Using API key registration for user: $usrName")
|
|
824
|
-
|
|
825
|
-
Log.d("OmikitPlugin", "🔑 Calling OmiClient.registerWithApiKey...")
|
|
826
795
|
omiClientMutex.withLock {
|
|
827
796
|
loginResult = OmiClient.registerWithApiKey(
|
|
828
797
|
apiKey ?: "",
|
|
@@ -834,16 +803,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
834
803
|
projectId
|
|
835
804
|
)
|
|
836
805
|
}
|
|
837
|
-
|
|
838
|
-
Log.d("OmikitPlugin", "🔑 OmiClient.registerWithApiKey returned: $loginResult")
|
|
839
|
-
|
|
840
|
-
if (loginResult) {
|
|
841
|
-
Log.d("OmikitPlugin", "✅ API key registration successful")
|
|
842
|
-
promise.resolve(true)
|
|
843
|
-
} else {
|
|
844
|
-
Log.e("OmikitPlugin", "❌ API key registration failed")
|
|
845
|
-
promise.resolve(false)
|
|
846
|
-
}
|
|
806
|
+
promise.resolve(loginResult)
|
|
847
807
|
} catch (e: Exception) {
|
|
848
808
|
Log.e("OmikitPlugin", "❌ Error during API key registration: ${e.message}", e)
|
|
849
809
|
promise.resolve(false)
|
|
@@ -855,21 +815,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
855
815
|
@ReactMethod
|
|
856
816
|
fun getInitialCall(counter: Int = 1, promise: Promise) {
|
|
857
817
|
val context = reactApplicationContext ?: run {
|
|
858
|
-
Log.e("getInitialCall", "❌ React context is null")
|
|
859
818
|
promise.resolve(false)
|
|
860
819
|
return
|
|
861
820
|
}
|
|
862
821
|
|
|
863
822
|
val call = Utils.getActiveCall(context)
|
|
864
|
-
Log.d("getInitialCall RN", "📞 Active call: $call")
|
|
865
|
-
|
|
866
823
|
if (call == null) {
|
|
867
824
|
if (counter <= 0) {
|
|
868
825
|
promise.resolve(false)
|
|
869
826
|
} else {
|
|
870
827
|
mainScope.launch {
|
|
871
|
-
|
|
872
|
-
delay(1000) // Wait 2 seconds
|
|
828
|
+
delay(1000) // Wait 1 second before retry
|
|
873
829
|
getInitialCall(counter - 1, promise) // Retry recursively
|
|
874
830
|
}
|
|
875
831
|
}
|
|
@@ -893,11 +849,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
893
849
|
statusPendingCall == 5 // 5 = User clicked pickup (CONFIRMED)
|
|
894
850
|
|
|
895
851
|
if (shouldAutoAnswer) {
|
|
896
|
-
Log.d("getInitialCall RN", "🚀 AUTO-ANSWER: User clicked pickup (statusPendingCall=$statusPendingCall), answering call immediately")
|
|
897
852
|
try {
|
|
898
853
|
OmiClient.getInstance(context).pickUp()
|
|
899
|
-
Log.d("getInitialCall RN", "✅ AUTO-ANSWER: Call answered successfully")
|
|
900
|
-
|
|
901
854
|
// Status already cleared by getStatusPendingCall()
|
|
902
855
|
} catch (e: Exception) {
|
|
903
856
|
Log.e("getInitialCall RN", "❌ AUTO-ANSWER: Failed to answer call: ${e.message}", e)
|
|
@@ -922,8 +875,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
922
875
|
promise.resolve(map)
|
|
923
876
|
|
|
924
877
|
if (statusPendingCall == 2 && call.state != 5) {
|
|
925
|
-
Log.d("getInitialCall RN", "🚀 Incoming Receive Triggered ($statusPendingCall)")
|
|
926
|
-
|
|
927
878
|
val eventMap: WritableMap = WritableNativeMap().apply {
|
|
928
879
|
putBoolean("isVideo", call.isVideo ?: false)
|
|
929
880
|
putBoolean("incoming", true)
|
|
@@ -946,25 +897,34 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
946
897
|
)
|
|
947
898
|
val map: WritableMap = WritableNativeMap()
|
|
948
899
|
if (audio == PackageManager.PERMISSION_GRANTED) {
|
|
949
|
-
currentActivity
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
900
|
+
val activity = currentActivity
|
|
901
|
+
if (activity == null) {
|
|
902
|
+
promise.reject("E_NO_ACTIVITY", "Current activity is null")
|
|
903
|
+
return
|
|
904
|
+
}
|
|
905
|
+
activity.runOnUiThread {
|
|
906
|
+
val phoneNumber = data.getString("phoneNumber")
|
|
907
|
+
if (phoneNumber.isNullOrEmpty()) {
|
|
908
|
+
promise.reject("E_INVALID_PHONE", "Phone number is required")
|
|
909
|
+
return@runOnUiThread
|
|
910
|
+
}
|
|
911
|
+
val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
|
|
912
|
+
|
|
953
913
|
val startCallResult =
|
|
954
914
|
OmiClient.getInstance(reactApplicationContext!!).startCall(phoneNumber, isVideo)
|
|
955
|
-
var statusCalltemp = startCallResult.value as Int
|
|
915
|
+
var statusCalltemp = startCallResult.value as Int
|
|
956
916
|
if (startCallResult.value == 200 || startCallResult.value == 407) {
|
|
957
917
|
statusCalltemp = 8
|
|
958
918
|
}
|
|
959
919
|
map.putInt("status", statusCalltemp)
|
|
960
920
|
map.putString("_id", "")
|
|
961
|
-
map.putString("message", messageCall(startCallResult.value)
|
|
921
|
+
map.putString("message", messageCall(startCallResult.value))
|
|
962
922
|
promise.resolve(map)
|
|
963
923
|
}
|
|
964
924
|
} else {
|
|
965
925
|
map.putInt("status", 4)
|
|
966
926
|
map.putString("_id", "")
|
|
967
|
-
map.putString("message", messageCall(406)
|
|
927
|
+
map.putString("message", messageCall(406))
|
|
968
928
|
promise.resolve(map)
|
|
969
929
|
}
|
|
970
930
|
}
|
|
@@ -976,28 +936,27 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
976
936
|
reactApplicationContext!!,
|
|
977
937
|
Manifest.permission.RECORD_AUDIO
|
|
978
938
|
)
|
|
979
|
-
Log.d("OMISDK", "📤 Start Call With UUID")
|
|
980
939
|
val map: WritableMap = WritableNativeMap()
|
|
981
940
|
if (audio == PackageManager.PERMISSION_GRANTED) {
|
|
982
941
|
mainScope.launch {
|
|
983
942
|
val uuid = data.getString("usrUuid") ?: ""
|
|
984
|
-
val isVideo = data.getBoolean("isVideo")
|
|
985
|
-
|
|
943
|
+
val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
|
|
944
|
+
|
|
986
945
|
val startCallResult =
|
|
987
946
|
OmiClient.getInstance(reactApplicationContext!!).startCallWithUuid(uuid, isVideo)
|
|
988
|
-
var statusCalltemp = startCallResult.value as Int
|
|
947
|
+
var statusCalltemp = startCallResult.value as Int
|
|
989
948
|
if (startCallResult.value == 200 || startCallResult.value == 407) {
|
|
990
949
|
statusCalltemp = 8
|
|
991
950
|
}
|
|
992
951
|
map.putInt("status", statusCalltemp)
|
|
993
952
|
map.putString("_id", "")
|
|
994
|
-
map.putString("message", messageCall(startCallResult.value)
|
|
953
|
+
map.putString("message", messageCall(startCallResult.value))
|
|
995
954
|
promise.resolve(map)
|
|
996
955
|
}
|
|
997
956
|
} else {
|
|
998
957
|
map.putInt("status", 4)
|
|
999
958
|
map.putString("_id", "")
|
|
1000
|
-
map.putString("message", messageCall(406)
|
|
959
|
+
map.putString("message", messageCall(406))
|
|
1001
960
|
promise.resolve(map)
|
|
1002
961
|
}
|
|
1003
962
|
}
|
|
@@ -1008,7 +967,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1008
967
|
val appContext = reactApplicationContext.applicationContext
|
|
1009
968
|
val activity = currentActivity
|
|
1010
969
|
|
|
1011
|
-
if (appContext == null) {
|
|
970
|
+
if (appContext == null) {
|
|
1012
971
|
promise.reject("E_NULL_CONTEXT", "Application context is null")
|
|
1013
972
|
return
|
|
1014
973
|
}
|
|
@@ -1044,15 +1003,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1044
1003
|
|
|
1045
1004
|
@ReactMethod
|
|
1046
1005
|
fun rejectCall(promise: Promise) {
|
|
1047
|
-
Log.d("OMISDK", "➡️ rejectCall called - isIncoming: $isIncoming, isAnswerCall: $isAnswerCall")
|
|
1048
1006
|
if (isIncoming) {
|
|
1049
|
-
Log.d("OMISDK", "📞 Incoming call")
|
|
1050
1007
|
ValidationHelper.safeOmiClientAccess(reactApplicationContext!!) { omiClient ->
|
|
1051
1008
|
if (!isAnswerCall) {
|
|
1052
|
-
Log.d("OMISDK", "🚫 Declining call with declineWithCode(true)")
|
|
1053
1009
|
omiClient.declineWithCode(true) // 486 Busy Here
|
|
1054
1010
|
} else {
|
|
1055
|
-
Log.d("OMISDK", "📴 Call already answered, hanging up")
|
|
1056
1011
|
omiClient.hangUp()
|
|
1057
1012
|
}
|
|
1058
1013
|
}
|
|
@@ -1060,7 +1015,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1060
1015
|
markCallEnded()
|
|
1061
1016
|
promise.resolve(true)
|
|
1062
1017
|
} else {
|
|
1063
|
-
Log.d("OMISDK", "📤 Not incoming call, skipping reject")
|
|
1064
1018
|
promise.resolve(false)
|
|
1065
1019
|
}
|
|
1066
1020
|
}
|
|
@@ -1120,7 +1074,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1120
1074
|
|
|
1121
1075
|
@ReactMethod
|
|
1122
1076
|
fun toggleSpeaker(promise: Promise) {
|
|
1123
|
-
currentActivity
|
|
1077
|
+
val activity = currentActivity
|
|
1078
|
+
if (activity == null) { promise.resolve(null); return }
|
|
1079
|
+
activity.runOnUiThread {
|
|
1124
1080
|
val newStatus = OmiClient.getInstance(reactApplicationContext!!).toggleSpeaker()
|
|
1125
1081
|
promise.resolve(newStatus)
|
|
1126
1082
|
sendEvent(SPEAKER, newStatus)
|
|
@@ -1129,7 +1085,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1129
1085
|
|
|
1130
1086
|
@ReactMethod
|
|
1131
1087
|
fun sendDTMF(data: ReadableMap, promise: Promise) {
|
|
1132
|
-
currentActivity
|
|
1088
|
+
val activity = currentActivity
|
|
1089
|
+
if (activity == null) { promise.resolve(false); return }
|
|
1090
|
+
activity.runOnUiThread {
|
|
1133
1091
|
val character = data.getString("character")
|
|
1134
1092
|
var characterCode: Int? = character?.toIntOrNull()
|
|
1135
1093
|
if (character == "*") {
|
|
@@ -1147,7 +1105,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1147
1105
|
|
|
1148
1106
|
@ReactMethod
|
|
1149
1107
|
fun switchOmiCamera(promise: Promise) {
|
|
1150
|
-
currentActivity
|
|
1108
|
+
val activity = currentActivity
|
|
1109
|
+
if (activity == null) { promise.resolve(false); return }
|
|
1110
|
+
activity.runOnUiThread {
|
|
1151
1111
|
OmiClient.getInstance(reactApplicationContext!!).switchCamera()
|
|
1152
1112
|
promise.resolve(true)
|
|
1153
1113
|
}
|
|
@@ -1155,7 +1115,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1155
1115
|
|
|
1156
1116
|
@ReactMethod
|
|
1157
1117
|
fun toggleOmiVideo(promise: Promise) {
|
|
1158
|
-
currentActivity
|
|
1118
|
+
val activity = currentActivity
|
|
1119
|
+
if (activity == null) { promise.resolve(false); return }
|
|
1120
|
+
activity.runOnUiThread {
|
|
1159
1121
|
OmiClient.getInstance(reactApplicationContext!!).toggleCamera()
|
|
1160
1122
|
promise.resolve(true)
|
|
1161
1123
|
}
|
|
@@ -1261,14 +1223,111 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1261
1223
|
}
|
|
1262
1224
|
}
|
|
1263
1225
|
|
|
1226
|
+
// MARK: - Getter Functions
|
|
1227
|
+
@ReactMethod
|
|
1228
|
+
fun getProjectId(promise: Promise) {
|
|
1229
|
+
try {
|
|
1230
|
+
val info = OmiClient.registrationInfo
|
|
1231
|
+
if (info?.projectId != null) {
|
|
1232
|
+
promise.resolve(info.projectId)
|
|
1233
|
+
return
|
|
1234
|
+
}
|
|
1235
|
+
// Fallback: get from Firebase project ID
|
|
1236
|
+
val firebaseProjectId = try {
|
|
1237
|
+
com.google.firebase.FirebaseApp.getInstance().options.projectId
|
|
1238
|
+
} catch (e: Exception) { null }
|
|
1239
|
+
promise.resolve(firebaseProjectId)
|
|
1240
|
+
} catch (e: Throwable) {
|
|
1241
|
+
promise.resolve(null)
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
@ReactMethod
|
|
1246
|
+
fun getAppId(promise: Promise) {
|
|
1247
|
+
try {
|
|
1248
|
+
val info = OmiClient.registrationInfo
|
|
1249
|
+
if (info?.appId != null) {
|
|
1250
|
+
promise.resolve(info.appId)
|
|
1251
|
+
return
|
|
1252
|
+
}
|
|
1253
|
+
// Fallback: get package name of the host app
|
|
1254
|
+
promise.resolve(reactApplicationContext?.packageName)
|
|
1255
|
+
} catch (e: Throwable) {
|
|
1256
|
+
promise.resolve(null)
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
@ReactMethod
|
|
1261
|
+
fun getDeviceId(promise: Promise) {
|
|
1262
|
+
try {
|
|
1263
|
+
val info = OmiClient.registrationInfo
|
|
1264
|
+
if (info?.deviceId != null) {
|
|
1265
|
+
promise.resolve(info.deviceId)
|
|
1266
|
+
return
|
|
1267
|
+
}
|
|
1268
|
+
// Fallback: get Android ID directly
|
|
1269
|
+
val androidId = try {
|
|
1270
|
+
Settings.Secure.getString(
|
|
1271
|
+
reactApplicationContext?.contentResolver,
|
|
1272
|
+
Settings.Secure.ANDROID_ID
|
|
1273
|
+
)
|
|
1274
|
+
} catch (e: Exception) { null }
|
|
1275
|
+
promise.resolve(androidId)
|
|
1276
|
+
} catch (e: Throwable) {
|
|
1277
|
+
promise.resolve(null)
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
@ReactMethod
|
|
1282
|
+
fun getFcmToken(promise: Promise) {
|
|
1283
|
+
try {
|
|
1284
|
+
val info = OmiClient.registrationInfo
|
|
1285
|
+
if (info?.firebaseToken != null) {
|
|
1286
|
+
promise.resolve(info.firebaseToken)
|
|
1287
|
+
return
|
|
1288
|
+
}
|
|
1289
|
+
// Fallback: get FCM token directly from Firebase Messaging
|
|
1290
|
+
try {
|
|
1291
|
+
com.google.firebase.messaging.FirebaseMessaging.getInstance().token
|
|
1292
|
+
.addOnSuccessListener { token -> promise.resolve(token) }
|
|
1293
|
+
.addOnFailureListener { promise.resolve(null) }
|
|
1294
|
+
} catch (e: Exception) {
|
|
1295
|
+
promise.resolve(null)
|
|
1296
|
+
}
|
|
1297
|
+
} catch (e: Throwable) {
|
|
1298
|
+
promise.resolve(null)
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
@ReactMethod
|
|
1303
|
+
fun getSipInfo(promise: Promise) {
|
|
1304
|
+
try {
|
|
1305
|
+
val sipUser = OmiClient.getInstance(reactApplicationContext!!).getSipUser()
|
|
1306
|
+
val sipRealm = OmiClient.getInstance(reactApplicationContext!!).getSipRealm()
|
|
1307
|
+
if (!sipUser.isNullOrEmpty() && !sipRealm.isNullOrEmpty()) {
|
|
1308
|
+
promise.resolve("$sipUser@$sipRealm")
|
|
1309
|
+
} else {
|
|
1310
|
+
promise.resolve(sipUser)
|
|
1311
|
+
}
|
|
1312
|
+
} catch (e: Throwable) {
|
|
1313
|
+
promise.resolve(null)
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
@ReactMethod
|
|
1318
|
+
fun getVoipToken(promise: Promise) {
|
|
1319
|
+
// VoIP token is iOS only, Android returns null
|
|
1320
|
+
promise.resolve(null)
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1264
1323
|
@ReactMethod
|
|
1265
1324
|
fun getAudio(promise: Promise) {
|
|
1266
1325
|
val inputs = OmiClient.getInstance(reactApplicationContext!!).getAudioOutputs()
|
|
1267
1326
|
val writeList = WritableNativeArray()
|
|
1268
1327
|
inputs.forEach {
|
|
1269
1328
|
val map = WritableNativeMap()
|
|
1270
|
-
map.putString("name", it["name"] as String)
|
|
1271
|
-
map.putInt("type", it["type"] as Int)
|
|
1329
|
+
map.putString("name", it["name"] as? String ?: "")
|
|
1330
|
+
map.putInt("type", it["type"] as? Int ?: 0)
|
|
1272
1331
|
writeList.pushMap(map)
|
|
1273
1332
|
}
|
|
1274
1333
|
promise.resolve(writeList)
|
|
@@ -1278,8 +1337,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1278
1337
|
fun getCurrentAudio(promise: Promise) {
|
|
1279
1338
|
val currentAudio = OmiClient.getInstance(reactApplicationContext!!).getCurrentAudio()
|
|
1280
1339
|
val map: WritableMap = WritableNativeMap()
|
|
1281
|
-
map.putString("name", currentAudio["name"] as String)
|
|
1282
|
-
map.putInt("type", currentAudio["type"] as Int)
|
|
1340
|
+
map.putString("name", currentAudio["name"] as? String ?: "")
|
|
1341
|
+
map.putInt("type", currentAudio["type"] as? Int ?: 0)
|
|
1283
1342
|
val writeList = WritableNativeArray()
|
|
1284
1343
|
writeList.pushMap(map)
|
|
1285
1344
|
promise.resolve(writeList)
|
|
@@ -1294,14 +1353,16 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1294
1353
|
|
|
1295
1354
|
@ReactMethod
|
|
1296
1355
|
fun transferCall(data: ReadableMap, promise: Promise) {
|
|
1297
|
-
currentActivity
|
|
1356
|
+
val activity = currentActivity
|
|
1357
|
+
if (activity == null) { promise.resolve(false); return }
|
|
1358
|
+
activity.runOnUiThread {
|
|
1298
1359
|
val phone = data.getString("phoneNumber")
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
OmiClient.getInstance(reactApplicationContext!!).forwardCallTo(phone as String)
|
|
1303
|
-
promise.resolve(true)
|
|
1360
|
+
if (phone.isNullOrEmpty()) {
|
|
1361
|
+
promise.reject("E_INVALID_PHONE", "Phone number is required for transfer")
|
|
1362
|
+
return@runOnUiThread
|
|
1304
1363
|
}
|
|
1364
|
+
OmiClient.getInstance(reactApplicationContext!!).forwardCallTo(phone)
|
|
1365
|
+
promise.resolve(true)
|
|
1305
1366
|
}
|
|
1306
1367
|
}
|
|
1307
1368
|
|
|
@@ -1310,6 +1371,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1310
1371
|
const val REQUEST_PERMISSIONS_CODE = 1001
|
|
1311
1372
|
const val REQUEST_OVERLAY_PERMISSION_CODE = 1002
|
|
1312
1373
|
|
|
1374
|
+
// Singleton reference for companion methods to access module instance
|
|
1375
|
+
@Volatile var moduleInstance: OmikitPluginModule? = null
|
|
1376
|
+
|
|
1313
1377
|
fun onDestroy() {
|
|
1314
1378
|
try {
|
|
1315
1379
|
// Cleanup OmiClient resources safely
|
|
@@ -1320,7 +1384,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1320
1384
|
|
|
1321
1385
|
fun onResume(act: ReactActivity) {
|
|
1322
1386
|
act.let { context ->
|
|
1323
|
-
Log.d("OMISDK_REACT", "=>> onResume => ")
|
|
1324
1387
|
OmiClient.getInstance(context, true)
|
|
1325
1388
|
OmiClient.isAppReady = true;
|
|
1326
1389
|
}
|
|
@@ -1349,37 +1412,28 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1349
1412
|
act: ReactActivity
|
|
1350
1413
|
) {
|
|
1351
1414
|
try {
|
|
1352
|
-
val deniedPermissions = mutableListOf<String>()
|
|
1353
1415
|
val grantedPermissions = mutableListOf<String>()
|
|
1354
|
-
|
|
1416
|
+
|
|
1355
1417
|
for (i in permissions.indices) {
|
|
1356
1418
|
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
|
1357
1419
|
grantedPermissions.add(permissions[i])
|
|
1358
|
-
} else {
|
|
1359
|
-
deniedPermissions.add(permissions[i])
|
|
1360
1420
|
}
|
|
1361
1421
|
}
|
|
1362
|
-
|
|
1363
|
-
Log.d("OmikitPlugin", "✅ Granted: ${grantedPermissions.joinToString()}")
|
|
1364
|
-
if (deniedPermissions.isNotEmpty()) {
|
|
1365
|
-
Log.w("OmikitPlugin", "❌ Denied: ${deniedPermissions.joinToString()}")
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1422
|
+
|
|
1368
1423
|
// Check if we have essential permissions for VoIP
|
|
1369
1424
|
val hasRecordAudio = grantedPermissions.contains(Manifest.permission.RECORD_AUDIO)
|
|
1370
1425
|
val hasCallPhone = grantedPermissions.contains(Manifest.permission.CALL_PHONE)
|
|
1371
1426
|
val hasModifyAudio = grantedPermissions.contains(Manifest.permission.MODIFY_AUDIO_SETTINGS)
|
|
1372
|
-
|
|
1427
|
+
|
|
1373
1428
|
val canProceed = hasRecordAudio && hasCallPhone && hasModifyAudio
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
Log.e("OmikitPlugin", "⚠️ Missing essential VoIP permissions - app may not work properly")
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1429
|
+
|
|
1430
|
+
// Resolve the stored permission promise
|
|
1431
|
+
moduleInstance?.permissionPromise?.resolve(canProceed)
|
|
1432
|
+
moduleInstance?.permissionPromise = null
|
|
1381
1433
|
} catch (e: Exception) {
|
|
1382
|
-
Log.e("OmikitPlugin", "
|
|
1434
|
+
Log.e("OmikitPlugin", "Error handling permission results: ${e.message}", e)
|
|
1435
|
+
moduleInstance?.permissionPromise?.resolve(false)
|
|
1436
|
+
moduleInstance?.permissionPromise = null
|
|
1383
1437
|
}
|
|
1384
1438
|
}
|
|
1385
1439
|
|
|
@@ -1392,7 +1446,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1392
1446
|
try {
|
|
1393
1447
|
val isIncoming = intent.getBooleanExtra(SipServiceConstants.ACTION_IS_INCOMING_CALL, false)
|
|
1394
1448
|
if (!isIncoming) {
|
|
1395
|
-
Log.d("PICKUP-FIX", "Not an incoming call intent, skipping")
|
|
1396
1449
|
return
|
|
1397
1450
|
}
|
|
1398
1451
|
|
|
@@ -1400,20 +1453,17 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1400
1453
|
SipServiceConstants.ACTION_ACCEPT_INCOMING_CALL, false
|
|
1401
1454
|
)
|
|
1402
1455
|
|
|
1403
|
-
Log.d("PICKUP-FIX", "🚀 Early intent handler - isIncoming: $isIncoming, isAccepted: $isAcceptedCall")
|
|
1404
1456
|
|
|
1405
1457
|
// Save to SharedPreferences so getInitialCall() can detect it later
|
|
1406
1458
|
// setStatusPendingCall(true) → saves status=5 (CONFIRMED)
|
|
1407
1459
|
// setStatusPendingCall(false) → saves status=2 (INCOMING)
|
|
1408
1460
|
OmiKitUtils().setStatusPendingCall(act, isAcceptedCall)
|
|
1409
|
-
Log.d("PICKUP-FIX", "✅ Saved pickup state to SharedPreferences (isAccepted=$isAcceptedCall)")
|
|
1410
1461
|
|
|
1411
1462
|
if (isAcceptedCall) {
|
|
1412
1463
|
// Try to answer immediately if possible (may fail if SDK not ready)
|
|
1413
1464
|
try {
|
|
1414
1465
|
OmiClient.getInstance(act, true)?.let { client ->
|
|
1415
1466
|
client.pickUp()
|
|
1416
|
-
Log.d("PICKUP-FIX", "✅ Successfully answered call immediately")
|
|
1417
1467
|
} ?: Log.w("PICKUP-FIX", "⚠️ OmiClient not ready, will auto-answer in getInitialCall()")
|
|
1418
1468
|
} catch (e: Exception) {
|
|
1419
1469
|
Log.w("PICKUP-FIX", "⚠️ Cannot answer immediately (SDK not ready): ${e.message}. Will auto-answer in getInitialCall()")
|
|
@@ -1440,7 +1490,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1440
1490
|
)
|
|
1441
1491
|
if (isReopenCall) {
|
|
1442
1492
|
val activeCall = Utils.getActiveCall(context)
|
|
1443
|
-
|
|
1493
|
+
// Use singleton module instance instead of creating a throwaway one
|
|
1494
|
+
moduleInstance?.onCallEstablished(
|
|
1444
1495
|
activeCall?.id ?: 0,
|
|
1445
1496
|
activeCall?.remoteNumber,
|
|
1446
1497
|
activeCall?.isVideo,
|
|
@@ -1453,7 +1504,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1453
1504
|
}
|
|
1454
1505
|
OmiKitUtils().setStatusPendingCall(context, isAcceptedCall)
|
|
1455
1506
|
}
|
|
1456
|
-
|
|
1457
1507
|
}
|
|
1458
1508
|
}
|
|
1459
1509
|
}
|
|
@@ -1461,19 +1511,16 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1461
1511
|
// ✅ Di chuyển sendEvent vào trong class để có thể access reactApplicationContext
|
|
1462
1512
|
private fun sendEvent(eventName: String?, params: Any?) {
|
|
1463
1513
|
if (eventName == null) {
|
|
1464
|
-
Log.e("OmikitPlugin", "❌ eventName is null or empty. Không thể gửi event.")
|
|
1465
1514
|
return
|
|
1466
1515
|
}
|
|
1467
1516
|
|
|
1468
1517
|
try {
|
|
1469
1518
|
// ✅ Kiểm tra reactApplicationContext
|
|
1470
1519
|
if (reactApplicationContext == null) {
|
|
1471
|
-
Log.e("OmikitPlugin", "❌ reactApplicationContext is null")
|
|
1472
1520
|
return
|
|
1473
1521
|
}
|
|
1474
1522
|
|
|
1475
1523
|
if (!reactApplicationContext.hasActiveReactInstance()) {
|
|
1476
|
-
Log.w("OmikitPlugin", "⚠️ ReactApplicationContext không có active React instance")
|
|
1477
1524
|
return
|
|
1478
1525
|
}
|
|
1479
1526
|
|
|
@@ -1498,45 +1545,29 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1498
1545
|
}
|
|
1499
1546
|
}
|
|
1500
1547
|
|
|
1501
|
-
// ✅ Thêm method để React Native biết các event được hỗ trợ
|
|
1502
|
-
override fun getConstants(): MutableMap<String, Any> {
|
|
1503
|
-
return hashMapOf(
|
|
1504
|
-
"CALL_STATE_CHANGED" to CALL_STATE_CHANGED,
|
|
1505
|
-
"MUTED" to MUTED,
|
|
1506
|
-
"HOLD" to HOLD,
|
|
1507
|
-
"SPEAKER" to SPEAKER,
|
|
1508
|
-
"CALL_QUALITY" to CALL_QUALITY,
|
|
1509
|
-
"AUDIO_CHANGE" to AUDIO_CHANGE,
|
|
1510
|
-
"SWITCHBOARD_ANSWER" to SWITCHBOARD_ANSWER,
|
|
1511
|
-
"REQUEST_PERMISSION" to REQUEST_PERMISSION,
|
|
1512
|
-
"CLICK_MISSED_CALL" to CLICK_MISSED_CALL,
|
|
1513
|
-
"AUTO_UNREGISTER_STATUS" to "AUTO_UNREGISTER_STATUS"
|
|
1514
|
-
)
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
1548
|
@ReactMethod
|
|
1518
1549
|
fun checkAndRequestPermissions(isVideo: Boolean, promise: Promise) {
|
|
1519
1550
|
try {
|
|
1520
1551
|
val missingPermissions = getMissingPermissions(isVideo)
|
|
1521
1552
|
|
|
1522
1553
|
if (missingPermissions.isEmpty()) {
|
|
1523
|
-
Log.d("OmikitPlugin", "✅ All permissions already granted")
|
|
1524
1554
|
promise.resolve(true)
|
|
1525
1555
|
return
|
|
1526
1556
|
}
|
|
1527
1557
|
|
|
1528
|
-
Log.d("OmikitPlugin", "📋 Missing permissions: ${missingPermissions.joinToString()}")
|
|
1529
|
-
|
|
1530
1558
|
// Store promise for callback
|
|
1531
1559
|
permissionPromise = promise
|
|
1532
1560
|
|
|
1561
|
+
val activity = reactApplicationContext?.currentActivity ?: run {
|
|
1562
|
+
promise.resolve(false)
|
|
1563
|
+
return
|
|
1564
|
+
}
|
|
1533
1565
|
ActivityCompat.requestPermissions(
|
|
1534
|
-
|
|
1566
|
+
activity,
|
|
1535
1567
|
missingPermissions.toTypedArray(),
|
|
1536
1568
|
REQUEST_PERMISSIONS_CODE,
|
|
1537
1569
|
)
|
|
1538
1570
|
} catch (e: Exception) {
|
|
1539
|
-
Log.e("OmikitPlugin", "❌ Error checking permissions: ${e.message}", e)
|
|
1540
1571
|
promise.resolve(false)
|
|
1541
1572
|
}
|
|
1542
1573
|
}
|
|
@@ -1621,15 +1652,16 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1621
1652
|
true
|
|
1622
1653
|
}
|
|
1623
1654
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1655
|
+
val map: WritableMap = WritableNativeMap()
|
|
1656
|
+
map.putArray("essentialGranted", Arguments.fromList(grantedEssential.toList()))
|
|
1657
|
+
map.putArray("essentialMissing", Arguments.fromList(missingEssential.toList()))
|
|
1658
|
+
map.putBoolean("canMakeVoipCalls", missingEssential.isEmpty())
|
|
1659
|
+
val fgServiceMap: WritableMap = WritableNativeMap()
|
|
1660
|
+
foregroundServiceStatus.forEach { (k, v) -> fgServiceMap.putBoolean(k, v) }
|
|
1661
|
+
map.putMap("foregroundServicePermissions", fgServiceMap)
|
|
1662
|
+
map.putBoolean("canDrawOverlays", canDrawOverlays)
|
|
1663
|
+
map.putInt("androidVersion", Build.VERSION.SDK_INT)
|
|
1664
|
+
map.putInt("targetSdk", reactApplicationContext.applicationInfo.targetSdkVersion)
|
|
1633
1665
|
promise.resolve(map)
|
|
1634
1666
|
|
|
1635
1667
|
} catch (e: Exception) {
|
|
@@ -1704,48 +1736,54 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1704
1736
|
// Store promise for callback
|
|
1705
1737
|
permissionPromise = promise
|
|
1706
1738
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1739
|
+
val activity = reactApplicationContext?.currentActivity ?: run {
|
|
1740
|
+
promise.reject("E_NULL_ACTIVITY", "Current activity is null")
|
|
1741
|
+
return
|
|
1742
|
+
}
|
|
1709
1743
|
ActivityCompat.requestPermissions(
|
|
1710
|
-
|
|
1744
|
+
activity,
|
|
1711
1745
|
permissionsToRequest.toTypedArray(),
|
|
1712
1746
|
REQUEST_PERMISSIONS_CODE
|
|
1713
1747
|
)
|
|
1714
1748
|
|
|
1715
1749
|
} catch (e: Exception) {
|
|
1716
|
-
Log.e("OmikitPlugin", "❌ Error requesting permissions by codes: ${e.message}", e)
|
|
1717
1750
|
promise.reject("ERROR_PERMISSION_REQUEST", "Failed to request permissions: ${e.message}")
|
|
1718
1751
|
}
|
|
1719
1752
|
}
|
|
1720
1753
|
|
|
1721
1754
|
private fun requestPermission(isVideo: Boolean) {
|
|
1722
1755
|
val missingPermissions = getMissingPermissions(isVideo)
|
|
1723
|
-
|
|
1756
|
+
|
|
1724
1757
|
if (missingPermissions.isEmpty()) {
|
|
1725
|
-
Log.d("OmikitPlugin", "✅ All permissions already granted")
|
|
1726
1758
|
return
|
|
1727
1759
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1760
|
+
|
|
1761
|
+
val activity = reactApplicationContext?.currentActivity ?: return
|
|
1731
1762
|
ActivityCompat.requestPermissions(
|
|
1732
|
-
|
|
1763
|
+
activity,
|
|
1733
1764
|
missingPermissions.toTypedArray(),
|
|
1734
1765
|
REQUEST_PERMISSIONS_CODE,
|
|
1735
1766
|
)
|
|
1736
1767
|
}
|
|
1737
1768
|
|
|
1738
|
-
override fun onActivityResult(
|
|
1769
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
1770
|
+
if (requestCode == REQUEST_OVERLAY_PERMISSION_CODE) {
|
|
1771
|
+
val granted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1772
|
+
Settings.canDrawOverlays(reactApplicationContext)
|
|
1773
|
+
} else {
|
|
1774
|
+
true
|
|
1775
|
+
}
|
|
1776
|
+
permissionPromise?.resolve(granted)
|
|
1777
|
+
permissionPromise = null
|
|
1778
|
+
}
|
|
1739
1779
|
}
|
|
1740
1780
|
|
|
1741
|
-
override fun onNewIntent(
|
|
1742
|
-
if (
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
if (p0.getBooleanExtra(SipServiceConstants.PARAM_IS_MISSED_CALL, false)) {
|
|
1781
|
+
override fun onNewIntent(intent: Intent) {
|
|
1782
|
+
if (intent.hasExtra(SipServiceConstants.PARAM_IS_MISSED_CALL)) {
|
|
1783
|
+
if (intent.getBooleanExtra(SipServiceConstants.PARAM_IS_MISSED_CALL, false)) {
|
|
1746
1784
|
val map: WritableMap = WritableNativeMap()
|
|
1747
|
-
map.putString("callerNumber",
|
|
1748
|
-
map.putBoolean("isVideo",
|
|
1785
|
+
map.putString("callerNumber", intent.getStringExtra(SipServiceConstants.PARAM_NUMBER) ?: "")
|
|
1786
|
+
map.putBoolean("isVideo", intent.getBooleanExtra(SipServiceConstants.PARAM_IS_VIDEO, false))
|
|
1749
1787
|
sendEvent(CLICK_MISSED_CALL, map)
|
|
1750
1788
|
}
|
|
1751
1789
|
}
|
|
@@ -1760,16 +1798,10 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1760
1798
|
}
|
|
1761
1799
|
}
|
|
1762
1800
|
|
|
1763
|
-
//
|
|
1801
|
+
// Auto-unregister listener - only handles onAutoUnregisterStatus
|
|
1802
|
+
// Other callbacks are NO-OP to prevent double-firing events (Fix #1)
|
|
1764
1803
|
private val autoUnregisterListener = object : OmiListener {
|
|
1765
1804
|
override fun onAutoUnregisterStatus(isScheduled: Boolean, timeUntilExecution: Long) {
|
|
1766
|
-
// ✅ Auto-unregister prevention removed - no longer supported in new SDK
|
|
1767
|
-
if (isScheduled && timeUntilExecution > 0 && timeUntilExecution < 3000) {
|
|
1768
|
-
Log.w("OmikitPlugin", "🚨 AUTO-UNREGISTER sắp thực hiện trong ${timeUntilExecution}ms - SDK tự xử lý")
|
|
1769
|
-
// preventAutoUnregisterCrash deprecated - SDK handles automatically
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
// ✅ Gửi event cho React Native
|
|
1773
1805
|
try {
|
|
1774
1806
|
val statusData = mapOf(
|
|
1775
1807
|
"isScheduled" to isScheduled,
|
|
@@ -1779,94 +1811,48 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1779
1811
|
val map = createSafeWritableMap(statusData)
|
|
1780
1812
|
sendEvent("AUTO_UNREGISTER_STATUS", map)
|
|
1781
1813
|
} catch (e: Exception) {
|
|
1782
|
-
Log.e("OmikitPlugin", "
|
|
1814
|
+
Log.e("OmikitPlugin", "Error sending AUTO_UNREGISTER_STATUS event: ${e.message}", e)
|
|
1783
1815
|
}
|
|
1784
1816
|
}
|
|
1785
|
-
|
|
1786
|
-
//
|
|
1787
|
-
override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {
|
|
1788
|
-
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
override fun
|
|
1792
|
-
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
override fun
|
|
1796
|
-
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
override fun
|
|
1800
|
-
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
override fun onDescriptionError() {
|
|
1804
|
-
this@OmikitPluginModule.onDescriptionError()
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
override fun onFcmReceived(uuid: String, userName: String, avatar: String) {
|
|
1808
|
-
this@OmikitPluginModule.onFcmReceived(uuid, userName, avatar)
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
override fun onRinging(callerId: Int, transactionId: String?) {
|
|
1812
|
-
this@OmikitPluginModule.onRinging(callerId, transactionId)
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
override fun networkHealth(stat: Map<String, *>, quality: Int) {
|
|
1816
|
-
this@OmikitPluginModule.networkHealth(stat, quality)
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
override fun onAudioChanged(audioInfo: Map<String, Any>) {
|
|
1820
|
-
this@OmikitPluginModule.onAudioChanged(audioInfo)
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
override fun onHold(isHold: Boolean) {
|
|
1824
|
-
this@OmikitPluginModule.onHold(isHold)
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
override fun onMuted(isMuted: Boolean) {
|
|
1828
|
-
this@OmikitPluginModule.onMuted(isMuted)
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
override fun onOutgoingStarted(callerId: Int, phoneNumber: String?, isVideo: Boolean?) {
|
|
1832
|
-
this@OmikitPluginModule.onOutgoingStarted(callerId, phoneNumber, isVideo)
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
override fun onSwitchBoardAnswer(sip: String) {
|
|
1836
|
-
this@OmikitPluginModule.onSwitchBoardAnswer(sip)
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
override fun onRegisterCompleted(statusCode: Int) {
|
|
1840
|
-
this@OmikitPluginModule.onRegisterCompleted(statusCode)
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
override fun onRequestPermission(permissions: Array<String>) {
|
|
1844
|
-
this@OmikitPluginModule.onRequestPermission(permissions)
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
override fun onVideoSize(width: Int, height: Int) {
|
|
1848
|
-
this@OmikitPluginModule.onVideoSize(width, height)
|
|
1849
|
-
}
|
|
1817
|
+
|
|
1818
|
+
// NO-OP: main module listener handles these - avoid double-firing
|
|
1819
|
+
override fun incomingReceived(callerId: Int?, phoneNumber: String?, isVideo: Boolean?) {}
|
|
1820
|
+
override fun onCallEstablished(callerId: Int, phoneNumber: String?, isVideo: Boolean?, startTime: Long, transactionId: String?) {}
|
|
1821
|
+
override fun onCallEnd(callInfo: MutableMap<String, Any?>, statusCode: Int) {}
|
|
1822
|
+
override fun onConnecting() {}
|
|
1823
|
+
override fun onDescriptionError() {}
|
|
1824
|
+
override fun onFcmReceived(uuid: String, userName: String, avatar: String) {}
|
|
1825
|
+
override fun onRinging(callerId: Int, transactionId: String?) {}
|
|
1826
|
+
override fun networkHealth(stat: Map<String, *>, quality: Int) {}
|
|
1827
|
+
override fun onAudioChanged(audioInfo: Map<String, Any>) {}
|
|
1828
|
+
override fun onHold(isHold: Boolean) {}
|
|
1829
|
+
override fun onMuted(isMuted: Boolean) {}
|
|
1830
|
+
override fun onOutgoingStarted(callerId: Int, phoneNumber: String?, isVideo: Boolean?) {}
|
|
1831
|
+
override fun onSwitchBoardAnswer(sip: String) {}
|
|
1832
|
+
override fun onRegisterCompleted(statusCode: Int) {}
|
|
1833
|
+
override fun onRequestPermission(permissions: Array<String>) {}
|
|
1834
|
+
override fun onVideoSize(width: Int, height: Int) {}
|
|
1850
1835
|
}
|
|
1851
1836
|
|
|
1852
1837
|
// ✅ Helper function để hide notification một cách an toàn
|
|
1853
1838
|
@ReactMethod
|
|
1854
1839
|
fun hideSystemNotificationSafely(promise: Promise) {
|
|
1855
1840
|
try {
|
|
1856
|
-
|
|
1857
|
-
|
|
1841
|
+
val context = reactApplicationContext ?: run {
|
|
1842
|
+
promise.resolve(false)
|
|
1843
|
+
return
|
|
1844
|
+
}
|
|
1845
|
+
// Delay to ensure registration completes before hiding
|
|
1846
|
+
mainScope.launch {
|
|
1858
1847
|
try {
|
|
1859
|
-
|
|
1860
|
-
OmiClient.getInstance(
|
|
1861
|
-
Log.d("OmikitPlugin", "✅ Successfully hidden system notification and unregistered")
|
|
1848
|
+
delay(2000)
|
|
1849
|
+
OmiClient.getInstance(context).hideSystemNotificationAndUnregister("Registration check completed")
|
|
1862
1850
|
promise.resolve(true)
|
|
1863
1851
|
} catch (e: Exception) {
|
|
1864
|
-
Log.e("OmikitPlugin", "❌ Failed to hide system notification: ${e.message}", e)
|
|
1865
1852
|
promise.resolve(false)
|
|
1866
1853
|
}
|
|
1867
|
-
}
|
|
1854
|
+
}
|
|
1868
1855
|
} catch (e: Exception) {
|
|
1869
|
-
Log.e("OmikitPlugin", "❌ Error in hideSystemNotificationSafely: ${e.message}", e)
|
|
1870
1856
|
promise.resolve(false)
|
|
1871
1857
|
}
|
|
1872
1858
|
}
|
|
@@ -1876,10 +1862,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1876
1862
|
fun hideSystemNotificationOnly(promise: Promise) {
|
|
1877
1863
|
try {
|
|
1878
1864
|
OmiClient.getInstance(reactApplicationContext!!).hideSystemNotification()
|
|
1879
|
-
Log.d("OmikitPlugin", "✅ Successfully hidden system notification (keeping registration)")
|
|
1880
1865
|
promise.resolve(true)
|
|
1881
1866
|
} catch (e: Exception) {
|
|
1882
|
-
Log.e("OmikitPlugin", "❌ Failed to hide system notification only: ${e.message}", e)
|
|
1883
1867
|
promise.resolve(false)
|
|
1884
1868
|
}
|
|
1885
1869
|
}
|
|
@@ -1889,10 +1873,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1889
1873
|
fun hideSystemNotificationAndUnregister(reason: String, promise: Promise) {
|
|
1890
1874
|
try {
|
|
1891
1875
|
OmiClient.getInstance(reactApplicationContext!!).hideSystemNotificationAndUnregister(reason)
|
|
1892
|
-
Log.d("OmikitPlugin", "✅ Successfully hidden notification and unregistered: $reason")
|
|
1893
1876
|
promise.resolve(true)
|
|
1894
1877
|
} catch (e: Exception) {
|
|
1895
|
-
Log.e("OmikitPlugin", "❌ Failed to hide notification and unregister: ${e.message}", e)
|
|
1896
1878
|
promise.resolve(false)
|
|
1897
1879
|
}
|
|
1898
1880
|
}
|
|
@@ -1904,21 +1886,18 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1904
1886
|
val userName = data.getString("userName")
|
|
1905
1887
|
val password = data.getString("password")
|
|
1906
1888
|
val realm = data.getString("realm")
|
|
1907
|
-
val host = data.getString("host")
|
|
1889
|
+
val host = data.getString("host").let { if (it.isNullOrEmpty()) "vh.omicrm.com" else it }
|
|
1908
1890
|
val firebaseToken = data.getString("fcmToken")
|
|
1909
1891
|
val projectId = data.getString("projectId") ?: ""
|
|
1910
1892
|
|
|
1911
1893
|
// Validate required parameters
|
|
1912
1894
|
if (userName.isNullOrEmpty() || password.isNullOrEmpty() || realm.isNullOrEmpty() || firebaseToken.isNullOrEmpty()) {
|
|
1913
|
-
Log.e("OmikitPlugin", "❌ Missing required parameters for credential check")
|
|
1914
1895
|
promise.resolve(mapOf("success" to false, "message" to "Missing required parameters"))
|
|
1915
1896
|
return@launch
|
|
1916
1897
|
}
|
|
1917
1898
|
|
|
1918
1899
|
withContext(Dispatchers.Default) {
|
|
1919
1900
|
try {
|
|
1920
|
-
Log.d("OmikitPlugin", "🔍 Checking credentials for user: $userName")
|
|
1921
|
-
|
|
1922
1901
|
OmiClient.getInstance(reactApplicationContext!!).checkCredentials(
|
|
1923
1902
|
userName = userName ?: "",
|
|
1924
1903
|
password = password ?: "",
|
|
@@ -1927,8 +1906,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1927
1906
|
host = host,
|
|
1928
1907
|
projectId = projectId
|
|
1929
1908
|
) { success, statusCode, message ->
|
|
1930
|
-
Log.d("OmikitPlugin", "🔍 Credential check callback - success: $success, status: $statusCode, message: $message")
|
|
1931
|
-
|
|
1932
1909
|
val result = mapOf(
|
|
1933
1910
|
"success" to success,
|
|
1934
1911
|
"statusCode" to statusCode,
|
|
@@ -1939,7 +1916,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1939
1916
|
}
|
|
1940
1917
|
|
|
1941
1918
|
} catch (e: Exception) {
|
|
1942
|
-
Log.e("OmikitPlugin", "❌ Error during credential check: ${e.message}", e)
|
|
1943
1919
|
val errorResult = mapOf(
|
|
1944
1920
|
"success" to false,
|
|
1945
1921
|
"message" to e.message
|
|
@@ -1957,24 +1933,21 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1957
1933
|
val userName = data.getString("userName")
|
|
1958
1934
|
val password = data.getString("password")
|
|
1959
1935
|
val realm = data.getString("realm")
|
|
1960
|
-
val host = data.getString("host")
|
|
1961
|
-
val isVideo = data.getBoolean("isVideo")
|
|
1936
|
+
val host = data.getString("host").let { if (it.isNullOrEmpty()) "vh.omicrm.com" else it }
|
|
1937
|
+
val isVideo = if (data.hasKey("isVideo")) data.getBoolean("isVideo") else false
|
|
1962
1938
|
val firebaseToken = data.getString("fcmToken")
|
|
1963
1939
|
val projectId = data.getString("projectId") ?: ""
|
|
1964
|
-
val showNotification = data.getBoolean("showNotification")
|
|
1965
|
-
val enableAutoUnregister = data.getBoolean("enableAutoUnregister")
|
|
1940
|
+
val showNotification = if (data.hasKey("showNotification")) data.getBoolean("showNotification") else true
|
|
1941
|
+
val enableAutoUnregister = if (data.hasKey("enableAutoUnregister")) data.getBoolean("enableAutoUnregister") else true
|
|
1966
1942
|
|
|
1967
1943
|
// Validate required parameters
|
|
1968
1944
|
if (userName.isNullOrEmpty() || password.isNullOrEmpty() || realm.isNullOrEmpty() || firebaseToken.isNullOrEmpty()) {
|
|
1969
|
-
Log.e("OmikitPlugin", "❌ Missing required parameters for registration with options")
|
|
1970
1945
|
promise.resolve(mapOf("success" to false, "message" to "Missing required parameters"))
|
|
1971
1946
|
return@launch
|
|
1972
1947
|
}
|
|
1973
1948
|
|
|
1974
1949
|
withContext(Dispatchers.Default) {
|
|
1975
1950
|
try {
|
|
1976
|
-
Log.d("OmikitPlugin", "⚙️ Registering with options for user: $userName - showNotification: $showNotification, enableAutoUnregister: $enableAutoUnregister")
|
|
1977
|
-
|
|
1978
1951
|
OmiClient.getInstance(reactApplicationContext!!).registerWithOptions(
|
|
1979
1952
|
userName = userName ?: "",
|
|
1980
1953
|
password = password ?: "",
|
|
@@ -1986,8 +1959,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1986
1959
|
showNotification = showNotification,
|
|
1987
1960
|
enableAutoUnregister = enableAutoUnregister
|
|
1988
1961
|
) { success, statusCode, message ->
|
|
1989
|
-
Log.d("OmikitPlugin", "⚙️ Registration with options callback - success: $success, status: $statusCode, message: $message")
|
|
1990
|
-
|
|
1991
1962
|
val result = mapOf(
|
|
1992
1963
|
"success" to success,
|
|
1993
1964
|
"statusCode" to statusCode,
|
|
@@ -1998,7 +1969,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1998
1969
|
}
|
|
1999
1970
|
|
|
2000
1971
|
} catch (e: Exception) {
|
|
2001
|
-
Log.e("OmikitPlugin", "❌ Error during registration with options: ${e.message}", e)
|
|
2002
1972
|
val errorResult = mapOf(
|
|
2003
1973
|
"success" to false,
|
|
2004
1974
|
"message" to e.message
|