omikit-plugin 3.3.19 → 3.3.21
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
CHANGED
|
@@ -235,7 +235,6 @@ public class MainActivity extends ReactActivity {
|
|
|
235
235
|
|
|
236
236
|
### ✅ For React Native > 0.74
|
|
237
237
|
|
|
238
|
-
|
|
239
238
|
```kotlin
|
|
240
239
|
class MainActivity : ReactActivity() {
|
|
241
240
|
// your config ....
|
|
@@ -243,6 +242,14 @@ class MainActivity : ReactActivity() {
|
|
|
243
242
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
244
243
|
super.onCreate(savedInstanceState)
|
|
245
244
|
|
|
245
|
+
intent?.let { intentData ->
|
|
246
|
+
try {
|
|
247
|
+
OmikitPluginModule.Companion.handlePickupIntentEarly(this, intentData)
|
|
248
|
+
} catch (e: Exception) {
|
|
249
|
+
Log.e("MainActivity", "⚠️ PICKUP-FIX: Error handling early intent: ${e.message}")
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
246
253
|
val reactInstanceManager: ReactInstanceManager = reactNativeHost.reactInstanceManager
|
|
247
254
|
val currentContext = reactInstanceManager.currentReactContext
|
|
248
255
|
if (currentContext != null && currentContext is ReactApplicationContext) {
|
|
@@ -263,21 +270,31 @@ class MainActivity : ReactActivity() {
|
|
|
263
270
|
}
|
|
264
271
|
|
|
265
272
|
override fun onNewIntent(intent: Intent?) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
273
|
+
super.onNewIntent(intent)
|
|
274
|
+
intent?.let { newIntent ->
|
|
275
|
+
Log.d("MainActivity", "🚀 PICKUP-FIX: New intent received (warm start)")
|
|
276
|
+
// IMPORTANT: Update the activity's intent to the new one
|
|
277
|
+
setIntent(newIntent)
|
|
278
|
+
try {
|
|
279
|
+
// Try to handle immediately if React context is ready
|
|
280
|
+
reactApplicationContext?.let {
|
|
281
|
+
OmikitPluginModule.Companion.onGetIntentFromNotification(it, newIntent, this)
|
|
282
|
+
} ?: run {
|
|
283
|
+
OmikitPluginModule.Companion.handlePickupIntentEarly(this, newIntent)
|
|
284
|
+
}
|
|
285
|
+
} catch (e: Exception) {
|
|
286
|
+
Log.e("MainActivity", "❌ PICKUP-FIX: Error in onNewIntent: ${e.message}")
|
|
287
|
+
}
|
|
288
|
+
} ?: Log.e("MainActivity", "Intent in onNewIntent is null.")
|
|
274
289
|
}
|
|
290
|
+
|
|
275
291
|
override fun onResume() {
|
|
276
292
|
super.onResume()
|
|
277
|
-
reactApplicationContext?.let {
|
|
293
|
+
reactApplicationContext?.let { context ->
|
|
278
294
|
OmikitPluginModule.Companion.onResume(this)
|
|
279
|
-
|
|
280
|
-
|
|
295
|
+
// Handle intent if exists (already updated by onNewIntent or from onCreate)
|
|
296
|
+
intent?.let { intentData ->
|
|
297
|
+
OmikitPluginModule.Companion.onGetIntentFromNotification(context, intentData, this)
|
|
281
298
|
}
|
|
282
299
|
} ?: Log.e("MainActivity", "ReactApplicationContext has not been initialized in onResume.")
|
|
283
300
|
}
|
package/android/build.gradle
CHANGED
|
@@ -122,7 +122,7 @@ dependencies {
|
|
|
122
122
|
implementation("androidx.work:work-runtime:2.8.1")
|
|
123
123
|
implementation "androidx.security:security-crypto:1.1.0-alpha06"
|
|
124
124
|
// api 'vn.vihat.omicall:omi-sdk:2.3.23'
|
|
125
|
-
api "io.omicrm.vihat:omi-sdk:2.4.
|
|
125
|
+
api "io.omicrm.vihat:omi-sdk:2.4.14"
|
|
126
126
|
|
|
127
127
|
implementation "com.facebook.react:react-native:+" // From node_modules
|
|
128
128
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
@@ -25,6 +25,8 @@ import kotlinx.coroutines.Dispatchers
|
|
|
25
25
|
import kotlinx.coroutines.launch
|
|
26
26
|
import kotlinx.coroutines.withContext
|
|
27
27
|
import kotlinx.coroutines.delay
|
|
28
|
+
import kotlinx.coroutines.sync.Mutex
|
|
29
|
+
import kotlinx.coroutines.sync.withLock
|
|
28
30
|
import vn.vihat.omicall.omisdk.OmiAccountListener
|
|
29
31
|
import vn.vihat.omicall.omisdk.OmiClient
|
|
30
32
|
import vn.vihat.omicall.omisdk.OmiListener
|
|
@@ -125,6 +127,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
125
127
|
private var lastCallTime: Long = 0
|
|
126
128
|
private val callCooldownMs: Long = 2000 // 2 seconds cooldown between calls
|
|
127
129
|
private val callStateLock = Any()
|
|
130
|
+
|
|
131
|
+
// Mutex for thread-safe OmiClient operations
|
|
132
|
+
private val omiClientMutex = Mutex()
|
|
128
133
|
|
|
129
134
|
override fun getName(): String {
|
|
130
135
|
return NAME
|
|
@@ -760,6 +765,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
760
765
|
|
|
761
766
|
@ReactMethod
|
|
762
767
|
fun initCallWithApiKey(data: ReadableMap, promise: Promise) {
|
|
768
|
+
Log.d("OmikitPlugin", "🔑 initCallWithApiKey called")
|
|
763
769
|
mainScope.launch {
|
|
764
770
|
var loginResult = false
|
|
765
771
|
val usrName = data.getString("fullName") ?: ""
|
|
@@ -769,47 +775,78 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
769
775
|
val phone = data.getString("phone")
|
|
770
776
|
val firebaseToken = data.getString("fcmToken") ?: ""
|
|
771
777
|
val projectId = data.getString("projectId") ?: ""
|
|
778
|
+
|
|
779
|
+
Log.d("OmikitPlugin", "🔑 Parameters - usrName: $usrName, usrUuid: $usrUuid, isVideo: $isVideo")
|
|
772
780
|
|
|
773
781
|
withContext(Dispatchers.Default) {
|
|
774
782
|
try {
|
|
783
|
+
Log.d("OmikitPlugin", "🔑 Starting validation")
|
|
775
784
|
// Validate required parameters
|
|
776
785
|
if (!ValidationHelper.validateRequired(mapOf(
|
|
777
786
|
"fullName" to usrName,
|
|
778
787
|
"usrUuid" to usrUuid,
|
|
779
788
|
"apiKey" to apiKey,
|
|
780
789
|
"fcmToken" to firebaseToken
|
|
781
|
-
), promise))
|
|
790
|
+
), promise)) {
|
|
791
|
+
Log.e("OmikitPlugin", "❌ Validation failed")
|
|
792
|
+
return@withContext
|
|
793
|
+
}
|
|
782
794
|
|
|
783
|
-
|
|
795
|
+
Log.d("OmikitPlugin", "✅ Validation passed")
|
|
796
|
+
|
|
797
|
+
// Check RECORD_AUDIO permission for Android 14+
|
|
798
|
+
val hasRecordAudio = ContextCompat.checkSelfPermission(
|
|
799
|
+
reactApplicationContext,
|
|
800
|
+
Manifest.permission.RECORD_AUDIO
|
|
801
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
802
|
+
|
|
803
|
+
if (!hasRecordAudio) {
|
|
804
|
+
Log.e("OmikitPlugin", "❌ RECORD_AUDIO permission is required for Android 14+")
|
|
805
|
+
promise.resolve(false)
|
|
806
|
+
return@withContext
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
Log.d("OmikitPlugin", "✅ RECORD_AUDIO permission granted")
|
|
810
|
+
|
|
811
|
+
// ✅ Cleanup trước khi register với mutex
|
|
784
812
|
try {
|
|
785
|
-
|
|
786
|
-
|
|
813
|
+
Log.d("OmikitPlugin", "🧹 Starting cleanup")
|
|
814
|
+
omiClientMutex.withLock {
|
|
815
|
+
OmiClient.getInstance(reactApplicationContext!!).logout()
|
|
816
|
+
}
|
|
817
|
+
delay(1000) // Chờ cleanup hoàn tất
|
|
818
|
+
Log.d("OmikitPlugin", "✅ Cleanup completed")
|
|
787
819
|
} catch (e: Exception) {
|
|
788
820
|
Log.w("OmikitPlugin", "⚠️ Cleanup warning (expected): ${e.message}")
|
|
789
821
|
}
|
|
790
822
|
|
|
791
823
|
Log.d("OmikitPlugin", "🔑 Using API key registration for user: $usrName")
|
|
792
824
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
825
|
+
Log.d("OmikitPlugin", "🔑 Calling OmiClient.registerWithApiKey...")
|
|
826
|
+
omiClientMutex.withLock {
|
|
827
|
+
loginResult = OmiClient.registerWithApiKey(
|
|
828
|
+
apiKey ?: "",
|
|
829
|
+
usrName ?: "",
|
|
830
|
+
usrUuid ?: "",
|
|
831
|
+
phone ?: "",
|
|
832
|
+
isVideo,
|
|
833
|
+
firebaseToken,
|
|
834
|
+
projectId
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
Log.d("OmikitPlugin", "🔑 OmiClient.registerWithApiKey returned: $loginResult")
|
|
802
839
|
|
|
803
840
|
if (loginResult) {
|
|
804
841
|
Log.d("OmikitPlugin", "✅ API key registration successful")
|
|
805
842
|
promise.resolve(true)
|
|
806
843
|
} else {
|
|
807
844
|
Log.e("OmikitPlugin", "❌ API key registration failed")
|
|
808
|
-
promise.
|
|
845
|
+
promise.resolve(false)
|
|
809
846
|
}
|
|
810
847
|
} catch (e: Exception) {
|
|
811
848
|
Log.e("OmikitPlugin", "❌ Error during API key registration: ${e.message}", e)
|
|
812
|
-
promise.
|
|
849
|
+
promise.resolve(false)
|
|
813
850
|
}
|
|
814
851
|
}
|
|
815
852
|
}
|
|
@@ -832,8 +869,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
832
869
|
} else {
|
|
833
870
|
mainScope.launch {
|
|
834
871
|
Log.d("getInitialCall RN", "🔄 Retrying in 2s... (Attempts left: $counter)")
|
|
835
|
-
delay(1000) //
|
|
836
|
-
getInitialCall(counter - 1, promise) //
|
|
872
|
+
delay(1000) // Wait 2 seconds
|
|
873
|
+
getInitialCall(counter - 1, promise) // Retry recursively
|
|
837
874
|
}
|
|
838
875
|
}
|
|
839
876
|
return
|
|
@@ -846,6 +883,26 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
846
883
|
}
|
|
847
884
|
|
|
848
885
|
val typeNumber = OmiKitUtils().checkTypeNumber(phoneNumber ?: "")
|
|
886
|
+
val statusPendingCall = OmiKitUtils().getStatusPendingCall(context)
|
|
887
|
+
|
|
888
|
+
// 🔥 CRITICAL FIX: Auto-answer if user already clicked pickup button
|
|
889
|
+
// statusPendingCall: 0 = no action, 2 = incoming (opened notification), 5 = accepted (clicked pickup)
|
|
890
|
+
val shouldAutoAnswer = call.direction == "inbound" &&
|
|
891
|
+
!call.isAccepted &&
|
|
892
|
+
call.state == 3 &&
|
|
893
|
+
statusPendingCall == 5 // 5 = User clicked pickup (CONFIRMED)
|
|
894
|
+
|
|
895
|
+
if (shouldAutoAnswer) {
|
|
896
|
+
Log.d("getInitialCall RN", "🚀 AUTO-ANSWER: User clicked pickup (statusPendingCall=$statusPendingCall), answering call immediately")
|
|
897
|
+
try {
|
|
898
|
+
OmiClient.getInstance(context).pickUp()
|
|
899
|
+
Log.d("getInitialCall RN", "✅ AUTO-ANSWER: Call answered successfully")
|
|
900
|
+
|
|
901
|
+
// Status already cleared by getStatusPendingCall()
|
|
902
|
+
} catch (e: Exception) {
|
|
903
|
+
Log.e("getInitialCall RN", "❌ AUTO-ANSWER: Failed to answer call: ${e.message}", e)
|
|
904
|
+
}
|
|
905
|
+
}
|
|
849
906
|
|
|
850
907
|
val map: WritableMap = WritableNativeMap().apply {
|
|
851
908
|
putString("callerNumber", phoneNumber)
|
|
@@ -855,9 +912,9 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
855
912
|
putBoolean("muted", false)
|
|
856
913
|
putBoolean("isVideo", call.isVideo ?: false)
|
|
857
914
|
putString("typeNumber", typeNumber)
|
|
915
|
+
putBoolean("autoAnswered", shouldAutoAnswer) // Add flag for RN side
|
|
858
916
|
}
|
|
859
917
|
|
|
860
|
-
val statusPendingCall = OmiKitUtils().getStatusPendingCall(context)
|
|
861
918
|
if (call.state == 3 && statusPendingCall != 0) {
|
|
862
919
|
call.state = statusPendingCall
|
|
863
920
|
}
|
|
@@ -867,7 +924,6 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
867
924
|
if (statusPendingCall == 2 && call.state != 5) {
|
|
868
925
|
Log.d("getInitialCall RN", "🚀 Incoming Receive Triggered ($statusPendingCall)")
|
|
869
926
|
|
|
870
|
-
|
|
871
927
|
val eventMap: WritableMap = WritableNativeMap().apply {
|
|
872
928
|
putBoolean("isVideo", call.isVideo ?: false)
|
|
873
929
|
putBoolean("incoming", true)
|
|
@@ -1327,6 +1383,47 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1327
1383
|
}
|
|
1328
1384
|
}
|
|
1329
1385
|
|
|
1386
|
+
/**
|
|
1387
|
+
* 🔥 CRITICAL FIX: Handle pickup intent EARLY (before React Native ready)
|
|
1388
|
+
* This method can be called from onCreate/onNewIntent when React context is not ready yet
|
|
1389
|
+
* It will save the pickup state to SharedPreferences for later processing in getInitialCall()
|
|
1390
|
+
*/
|
|
1391
|
+
fun handlePickupIntentEarly(act: Activity, intent: Intent) {
|
|
1392
|
+
try {
|
|
1393
|
+
val isIncoming = intent.getBooleanExtra(SipServiceConstants.ACTION_IS_INCOMING_CALL, false)
|
|
1394
|
+
if (!isIncoming) {
|
|
1395
|
+
Log.d("PICKUP-FIX", "Not an incoming call intent, skipping")
|
|
1396
|
+
return
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
val isAcceptedCall = intent.getBooleanExtra(
|
|
1400
|
+
SipServiceConstants.ACTION_ACCEPT_INCOMING_CALL, false
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
Log.d("PICKUP-FIX", "🚀 Early intent handler - isIncoming: $isIncoming, isAccepted: $isAcceptedCall")
|
|
1404
|
+
|
|
1405
|
+
// Save to SharedPreferences so getInitialCall() can detect it later
|
|
1406
|
+
// setStatusPendingCall(true) → saves status=5 (CONFIRMED)
|
|
1407
|
+
// setStatusPendingCall(false) → saves status=2 (INCOMING)
|
|
1408
|
+
OmiKitUtils().setStatusPendingCall(act, isAcceptedCall)
|
|
1409
|
+
Log.d("PICKUP-FIX", "✅ Saved pickup state to SharedPreferences (isAccepted=$isAcceptedCall)")
|
|
1410
|
+
|
|
1411
|
+
if (isAcceptedCall) {
|
|
1412
|
+
// Try to answer immediately if possible (may fail if SDK not ready)
|
|
1413
|
+
try {
|
|
1414
|
+
OmiClient.getInstance(act, true)?.let { client ->
|
|
1415
|
+
client.pickUp()
|
|
1416
|
+
Log.d("PICKUP-FIX", "✅ Successfully answered call immediately")
|
|
1417
|
+
} ?: Log.w("PICKUP-FIX", "⚠️ OmiClient not ready, will auto-answer in getInitialCall()")
|
|
1418
|
+
} catch (e: Exception) {
|
|
1419
|
+
Log.w("PICKUP-FIX", "⚠️ Cannot answer immediately (SDK not ready): ${e.message}. Will auto-answer in getInitialCall()")
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
} catch (e: Exception) {
|
|
1423
|
+
Log.e("PICKUP-FIX", "❌ Error in handlePickupIntentEarly: ${e.message}", e)
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1330
1427
|
fun onGetIntentFromNotification(
|
|
1331
1428
|
context: ReactApplicationContext,
|
|
1332
1429
|
intent: Intent,
|