@qusaieilouti99/call-manager 0.1.184 → 0.1.185

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.
@@ -320,7 +320,9 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
320
320
  finishReason = FinishReason.ANSWER
321
321
  CallEngine.stopRingtone()
322
322
  CallEngine.cancelIncomingCallUI()
323
- CallEngine.answerCall(callId)
323
+
324
+ // Use the unified answer flow with isLocalAnswer = true
325
+ CallEngine.answerCall(callId, isLocalAnswer = true)
324
326
  finishCallActivity()
325
327
  }
326
328
 
@@ -80,6 +80,7 @@ object CallEngine {
80
80
  private val activeCalls = ConcurrentHashMap<String, CallInfo>()
81
81
  private val telecomConnections = ConcurrentHashMap<String, Connection>()
82
82
  private val callMetadata = ConcurrentHashMap<String, String>()
83
+ private val callAnswerStates = ConcurrentHashMap<String, Boolean>()
83
84
 
84
85
  private var currentCallId: String? = null
85
86
  private var canMakeMultipleCalls: Boolean = false
@@ -205,11 +206,14 @@ object CallEngine {
205
206
 
206
207
  fun removeTelecomConnection(callId: String) {
207
208
  telecomConnections.remove(callId)
209
+ callAnswerStates.remove(callId) // Clean up answer state
208
210
  Log.d(TAG, "Removed Telecom Connection for callId: $callId")
209
211
  }
210
212
 
211
213
  fun getTelecomConnection(callId: String): Connection? = telecomConnections[callId]
212
214
 
215
+ fun getCallAnswerState(callId: String): Boolean? = callAnswerStates.remove(callId)
216
+
213
217
  fun setCanMakeMultipleCalls(allow: Boolean) {
214
218
  canMakeMultipleCalls = allow
215
219
  Log.d(TAG, "canMakeMultipleCalls set to: $allow")
@@ -329,7 +333,6 @@ object CallEngine {
329
333
  currentCallId = callId
330
334
  Log.d(TAG, "Call $callId added to activeCalls. State: DIALING")
331
335
 
332
- // Removed setAudioMode() call from here as per user feedback to fix audio routing issues.
333
336
  registerPhoneAccount()
334
337
 
335
338
  val telecomManager =
@@ -380,13 +383,13 @@ object CallEngine {
380
383
  if (existingCallInfo != null && existingCallInfo.state == CallState.INCOMING) {
381
384
  // Scenario 1: Call with this ID is already incoming, answer it.
382
385
  Log.d(TAG, "Call $callId is incoming, answering it directly via startCall.")
383
- coreCallAnswered(callId, isLocalAnswer = false) // isLocalAnswer = false as it's not a direct answer action from the user on an incoming call.
384
- return // Crucial: Exit after handling an incoming call to prevent new call initiation.
386
+ answerCall(callId, isLocalAnswer = false) // Remote party answered
387
+ return
385
388
  }
386
389
 
387
390
  // Scenario 2: Call is new or not incoming, treat as new outgoing call that is immediately active.
388
391
  Log.d(TAG, "Call $callId is new or not incoming. Initiating as outgoing and immediately active.")
389
- if (!canMakeMultipleCalls && activeCalls.isNotEmpty()) { // Only check if multiple calls are not allowed
392
+ if (!canMakeMultipleCalls && activeCalls.isNotEmpty()) {
390
393
  if (!validateOutgoingCallRequest()) {
391
394
  Log.w(TAG, "Rejecting startCall as outgoing - incoming/active call exists")
392
395
  emitEvent(CallEventType.CALL_REJECTED, JSONObject().apply {
@@ -406,11 +409,10 @@ object CallEngine {
406
409
  }
407
410
  }
408
411
 
409
- activeCalls[callId] = CallInfo(callId, callType, targetName, null, CallState.DIALING) // Temporarily DIALING for Telecom
412
+ activeCalls[callId] = CallInfo(callId, callType, targetName, null, CallState.DIALING)
410
413
  currentCallId = callId
411
414
  Log.d(TAG, "Call $callId added to activeCalls. Initial state: DIALING (for Telecom)")
412
415
 
413
- // Removed setAudioMode() call from here as per user feedback to fix audio routing issues.
414
416
  registerPhoneAccount()
415
417
 
416
418
  val telecomManager = requireContext().getSystemService(Context.TELECOM_SERVICE) as TelecomManager
@@ -428,7 +430,7 @@ object CallEngine {
428
430
  val placeCallExtras = Bundle().apply {
429
431
  putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
430
432
  putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, outgoingExtrasForConnectionService)
431
- putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isVideoCall) // Hint for video calls
433
+ putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isVideoCall)
432
434
  putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, if (isVideoCall) VideoProfile.STATE_BIDIRECTIONAL else VideoProfile.STATE_AUDIO_ONLY)
433
435
  }
434
436
 
@@ -440,7 +442,7 @@ object CallEngine {
440
442
  Log.d(TAG, "Successfully reported outgoing call (to be immediately active) to TelecomManager for $callId")
441
443
 
442
444
  // Immediately mark as answered for "startCall" behavior
443
- coreCallAnswered(callId, isLocalAnswer = false) // isLocalAnswer = false as it's not a direct answer action from the user on an incoming call.
445
+ answerCall(callId, isLocalAnswer = false) // Remote party answered
444
446
  } catch (e: Exception) {
445
447
  Log.e(TAG, "Failed to start call as active: ${e.message}", e)
446
448
  endCallInternal(callId)
@@ -448,17 +450,29 @@ object CallEngine {
448
450
  updateLockScreenBypass()
449
451
  }
450
452
 
451
- fun callAnsweredFromJS(callId: String) {
452
- Log.d(TAG, "callAnsweredFromJS: $callId - remote party answered")
453
- coreCallAnswered(callId, isLocalAnswer = false)
454
- }
453
+ // NEW UNIFIED ANSWER METHOD
454
+ fun answerCall(callId: String, isLocalAnswer: Boolean = true) {
455
+ Log.d(TAG, "answerCall: $callId, isLocalAnswer: $isLocalAnswer")
456
+ val callInfo = activeCalls[callId]
457
+ if (callInfo == null) {
458
+ Log.w(TAG, "Cannot answer call $callId - not found in active calls")
459
+ return
460
+ }
461
+
462
+ // Store the isLocalAnswer state for the connection to use
463
+ callAnswerStates[callId] = isLocalAnswer
455
464
 
456
- fun answerCall(callId: String) {
457
- Log.d(TAG, "answerCall: $callId - local party answering")
458
- coreCallAnswered(callId, isLocalAnswer = true)
465
+ // Always call connection.onAnswer() to let Telecom handle the flow
466
+ telecomConnections[callId]?.let { connection ->
467
+ connection.onAnswer() // This will trigger MyConnection.onAnswer()
468
+ } ?: run {
469
+ Log.w(TAG, "No telecom connection found for $callId, falling back to direct answer")
470
+ coreCallAnswered(callId, isLocalAnswer)
471
+ }
459
472
  }
460
473
 
461
- private fun coreCallAnswered(callId: String, isLocalAnswer: Boolean) {
474
+ // INTERNAL METHOD called by MyConnection.onAnswer()
475
+ internal fun coreCallAnswered(callId: String, isLocalAnswer: Boolean) {
462
476
  Log.d(TAG, "coreCallAnswered: $callId, isLocalAnswer: $isLocalAnswer")
463
477
  val callInfo = activeCalls[callId]
464
478
  if (callInfo == null) {
@@ -489,7 +503,7 @@ object CallEngine {
489
503
  keepScreenAwake(true)
490
504
  updateLockScreenBypass()
491
505
 
492
- setAudioMode() // Ensure audio mode is correct for an active call.
506
+ setAudioMode()
493
507
 
494
508
  // Set initial audio route using Telecom's CallEndpoint API
495
509
  setInitialCallAudioRoute(callId, callInfo.callType)
@@ -614,8 +628,6 @@ object CallEngine {
614
628
  setMutedInternal(callId, muted)
615
629
  }
616
630
 
617
- // This method is now called by MyConnection.onMuteStateChanged which receives state from Telecom.
618
- // It applies the mute state to AudioManager and emits the event.
619
631
  private fun setMutedInternal(callId: String, muted: Boolean) {
620
632
  val callInfo = activeCalls[callId]
621
633
  if (callInfo == null) {
@@ -630,7 +642,6 @@ object CallEngine {
630
642
  audioManager?.isMicrophoneMute = muted
631
643
  Log.d(TAG, "AudioManager microphone mute set to: $muted")
632
644
 
633
-
634
645
  if (wasMuted != muted) {
635
646
  val eventType = if (muted) CallEventType.CALL_MUTED else CallEventType.CALL_UNMUTED
636
647
  emitEvent(eventType, JSONObject().put("callId", callId))
@@ -654,6 +665,7 @@ object CallEngine {
654
665
  activeCalls.clear()
655
666
  telecomConnections.clear()
656
667
  callMetadata.clear()
668
+ callAnswerStates.clear()
657
669
  currentCallId = null
658
670
 
659
671
  cleanup()
@@ -670,6 +682,7 @@ object CallEngine {
670
682
 
671
683
  val metadata = callMetadata.remove(callId)
672
684
  activeCalls.remove(callId)
685
+ callAnswerStates.remove(callId) // Clean up answer state
673
686
 
674
687
  stopRingback()
675
688
  stopRingtone()
@@ -693,7 +706,6 @@ object CallEngine {
693
706
  }
694
707
 
695
708
  telecomConnections[callId]?.let { connection ->
696
- // Disconnect and destroy the Telecom Connection
697
709
  connection.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
698
710
  connection.destroy()
699
711
  removeTelecomConnection(callId)
@@ -728,14 +740,12 @@ object CallEngine {
728
740
 
729
741
  // ====== IMPROVED AUDIO ROUTING SYSTEM (using CallEndpoint API) ======
730
742
 
731
- // Stores the currently available CallEndpoints as reported by Telecom
732
743
  fun onTelecomAvailableEndpointsChanged(endpoints: List<CallEndpoint>) {
733
744
  availableCallEndpoints = endpoints
734
745
  Log.d(TAG, "Available CallEndpoints updated: ${endpoints.map { "${it.endpointName}(${mapCallEndpointTypeToString(it.endpointType)})" }}")
735
746
  emitAudioDevicesChanged()
736
747
  }
737
748
 
738
- // Stores the active CallEndpoint as reported by Telecom
739
749
  fun onTelecomAudioRouteChanged(callId: String, callEndpoint: CallEndpoint) {
740
750
  Log.d(TAG, "Telecom audio route changed for $callId: endpoint=${callEndpoint.endpointName} (type=${mapCallEndpointTypeToString(callEndpoint.endpointType)})")
741
751
  currentActiveCallEndpoint = callEndpoint
@@ -745,9 +755,6 @@ object CallEngine {
745
755
  fun getAudioDevices(): AudioRoutesInfo {
746
756
  val devices = availableCallEndpoints.map { StringHolder(mapCallEndpointTypeToString(it.endpointType)) }.toMutableSet()
747
757
 
748
- // Add common fallback endpoints if Telecom doesn't explicitly list them,
749
- // although for active calls, Telecom should provide comprehensive lists.
750
- // This provides robustness for the JavaScript side's expected string values.
751
758
  if (!devices.any { it.value == "Earpiece" }) devices.add(StringHolder("Earpiece"))
752
759
  if (!devices.any { it.value == "Speaker" }) devices.add(StringHolder("Speaker"))
753
760
 
@@ -763,9 +770,8 @@ object CallEngine {
763
770
 
764
771
  val telecomEndpointType = mapStringToCallEndpointType(route)
765
772
 
766
- // Find the actual CallEndpoint object from the available list that matches the type
767
773
  val targetEndpoint = availableCallEndpoints.find { it.endpointType == telecomEndpointType }
768
- ?: getOrCreateGenericCallEndpoint(telecomEndpointType, route) // Fallback to create generic if not found in available list
774
+ ?: getOrCreateGenericCallEndpoint(telecomEndpointType, route)
769
775
 
770
776
  if (targetEndpoint != null) {
771
777
  currentCallId?.let { callId ->
@@ -783,7 +789,6 @@ object CallEngine {
783
789
  }
784
790
  }
785
791
 
786
- // This method is called by MyConnection when its state becomes ACTIVE, or after startCall/startOutgoingCall.
787
792
  fun setInitialCallAudioRoute(callId: String, callType: String) {
788
793
  Log.d(TAG, "Setting initial audio route for callId: $callId, type: $callType")
789
794
 
@@ -799,9 +804,6 @@ object CallEngine {
799
804
  else -> CallEndpoint.TYPE_EARPIECE
800
805
  }
801
806
 
802
- // We should ideally pick from `availableCallEndpoints`.
803
- // If none of that type are explicitly listed (e.g. for default Earpiece/Speaker),
804
- // we can try to create a generic one.
805
807
  val targetEndpoint = availableCallEndpoints.find { it.endpointType == targetEndpointType }
806
808
  ?: getOrCreateGenericCallEndpoint(targetEndpointType, mapCallEndpointTypeToString(targetEndpointType))
807
809
 
@@ -813,7 +815,7 @@ object CallEngine {
813
815
  connection.setTelecomAudioRoute(targetEndpoint)
814
816
  }
815
817
  } ?: Log.w(TAG, "No telecom connection found for $callId during initial route setting.")
816
- }, 500L) // Small delay to allow Telecom to process connection activation
818
+ }, 500L)
817
819
  } else {
818
820
  Log.w(TAG, "Could not find or create a valid CallEndpoint for initial route type: $targetEndpointType")
819
821
  }
@@ -833,8 +835,8 @@ object CallEngine {
833
835
  }
834
836
  am.isSpeakerphoneOn = false
835
837
  }
836
- currentActiveCallEndpoint = null // Reset active endpoint
837
- availableCallEndpoints = emptyList() // Clear available endpoints
838
+ currentActiveCallEndpoint = null
839
+ availableCallEndpoints = emptyList()
838
840
  wasManuallySetAudioRoute = false
839
841
  Log.d(TAG, "Audio mode reset to MODE_NORMAL, audio endpoints reset.")
840
842
  }
@@ -863,8 +865,6 @@ object CallEngine {
863
865
  }
864
866
 
865
867
  private fun getOrCreateGenericCallEndpoint(type: Int, name: String): CallEndpoint? {
866
- // This is a fallback to create a CallEndpoint if it's not explicitly in availableCallEndpoints.
867
- // Useful for basic types like Earpiece/Speaker that might implicitly exist.
868
868
  return when (type) {
869
869
  CallEndpoint.TYPE_EARPIECE -> CallEndpoint(name, type, ParcelUuid(UUID.nameUUIDFromBytes("Earpiece_Default".toByteArray())))
870
870
  CallEndpoint.TYPE_SPEAKER -> CallEndpoint(name, type, ParcelUuid(UUID.nameUUIDFromBytes("Speaker_Default".toByteArray())))
@@ -874,7 +874,6 @@ object CallEngine {
874
874
  }
875
875
  }
876
876
 
877
-
878
877
  private fun isWiredHeadsetConnected(): Boolean {
879
878
  val am = audioManager ?: return false
880
879
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -906,7 +905,7 @@ object CallEngine {
906
905
  }
907
906
 
908
907
  private fun emitAudioRouteChanged(currentRoute: String) {
909
- val info = getAudioDevices() // This now uses CallEngine's internal state
908
+ val info = getAudioDevices()
910
909
  val deviceStrings = info.devices.map { it.value }
911
910
  val payload = JSONObject().apply {
912
911
  put("devices", JSONArray(deviceStrings))
@@ -927,10 +926,6 @@ object CallEngine {
927
926
  Log.d(TAG, "Audio devices changed: available: $deviceStrings")
928
927
  }
929
928
 
930
- // Audio device callbacks are now handled by Telecom and MyConnection.
931
- // The CallEngine does not directly register an AudioDeviceCallback.
932
- // This part of the code is removed.
933
-
934
929
  // ====== END IMPROVED AUDIO ROUTING SYSTEM ======
935
930
 
936
931
  fun keepScreenAwake(keepAwake: Boolean) {
@@ -938,13 +933,10 @@ object CallEngine {
938
933
  val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
939
934
  if (keepAwake) {
940
935
  if (wakeLock == null || wakeLock!!.isHeld.not()) {
941
- // Use PARTIAL_WAKE_LOCK to keep CPU awake, SCREEN_DIM_WAKE_LOCK to keep screen on (dim)
942
- // For general "keep screen awake", SCREEN_DIM_WAKE_LOCK is appropriate.
943
936
  wakeLock = powerManager.newWakeLock(
944
937
  PowerManager.SCREEN_DIM_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
945
938
  "CallEngine:WakeLock"
946
939
  )
947
- // Set a timeout for the wakelock to prevent indefinite drain, e.g., 10 minutes
948
940
  wakeLock?.acquire(10 * 60 * 1000L)
949
941
  Log.d(TAG, "Acquired SCREEN_DIM_WAKE_LOCK")
950
942
  }
@@ -969,20 +961,17 @@ object CallEngine {
969
961
  }
970
962
 
971
963
  private fun validateOutgoingCallRequest(): Boolean {
972
- // Only allow outgoing call if no incoming or active call exists and multi-call not allowed
973
964
  return !activeCalls.any {
974
965
  (!canMakeMultipleCalls && (it.value.state == CallState.INCOMING || it.value.state == CallState.ACTIVE))
975
966
  }
976
967
  }
977
968
 
978
-
979
969
  private fun rejectIncomingCallCollision(callId: String, reason: String) {
980
970
  emitEvent(CallEventType.CALL_REJECTED, JSONObject().apply {
981
971
  put("callId", callId)
982
972
  put("reason", reason)
983
973
  })
984
974
 
985
- // Only remove metadata if there's NO existing active call with this ID
986
975
  val existingCall = activeCalls[callId]
987
976
  if (existingCall == null) {
988
977
  callMetadata.remove(callId)
@@ -1006,12 +995,6 @@ object CallEngine {
1006
995
  channel.enableVibration(true)
1007
996
  channel.setBypassDnd(true)
1008
997
  channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
1009
-
1010
- // For Android S (API 31) and above, Telecom manages ringing for self-managed calls
1011
- // if it's the default dialer or an InCallService with METADATA_IN_CALL_SERVICE_RINGING.
1012
- // Setting sound to null here allows Telecom to take over or for us to manage manually
1013
- // if not relying on Telecom for ringing.
1014
- // Since we play ringtone manually below, ensure channel sound is null to avoid double ringing.
1015
998
  channel.setSound(null, null)
1016
999
  channel.importance = NotificationManager.IMPORTANCE_HIGH
1017
1000
 
@@ -1027,12 +1010,11 @@ object CallEngine {
1027
1010
  val useCallStyleNotification = supportsCallStyleNotifications()
1028
1011
  Log.d(TAG, "Using CallStyle notification: $useCallStyleNotification")
1029
1012
 
1030
- // Determine if device is locked or if CallStyle is not preferred/supported for current scenario
1031
1013
  val isDeviceLocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
1032
1014
  val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
1033
1015
  keyguardManager.isKeyguardLocked
1034
1016
  } else {
1035
- false // Older APIs, assume no direct lock screen check needed or handled differently
1017
+ false
1036
1018
  }
1037
1019
 
1038
1020
  if (isDeviceLocked || !useCallStyleNotification) {
@@ -1066,7 +1048,7 @@ object CallEngine {
1066
1048
  PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
1067
1049
  "CallEngine:LockScreenWake"
1068
1050
  )
1069
- wakeLock.acquire(5000) // Acquire for a short duration
1051
+ wakeLock.acquire(5000)
1070
1052
  context.startActivity(overlayIntent)
1071
1053
  Log.d(TAG, "Successfully launched CallActivity overlay")
1072
1054
  } catch (e: Exception) {
@@ -1130,7 +1112,7 @@ object CallEngine {
1130
1112
  .setCategory(Notification.CATEGORY_CALL)
1131
1113
  .setPriority(Notification.PRIORITY_MAX)
1132
1114
  .setVisibility(Notification.VISIBILITY_PUBLIC)
1133
- .setSound(null) // No sound for CallStyle, we manage it separately
1115
+ .setSound(null)
1134
1116
  .build()
1135
1117
  } else {
1136
1118
  Notification.Builder(context, NOTIF_CHANNEL_ID)
@@ -1145,7 +1127,7 @@ object CallEngine {
1145
1127
  .setOngoing(true)
1146
1128
  .setAutoCancel(false)
1147
1129
  .setVisibility(Notification.VISIBILITY_PUBLIC)
1148
- .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)) // Fallback for older styles
1130
+ .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))
1149
1131
  .build()
1150
1132
  }
1151
1133
 
@@ -1199,12 +1181,10 @@ object CallEngine {
1199
1181
  val activityManager =
1200
1182
  context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
1201
1183
 
1202
- // For API 23+, getRunningTasks is deprecated. Use UsageStatsManager or check AppTasks
1203
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { // LMR1 is API 22, getAppTasks available from API 21, but getRunningTasks deprecated from API 23.
1184
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
1204
1185
  try {
1205
1186
  val tasks = activityManager.appTasks
1206
1187
  if (tasks.isNotEmpty()) {
1207
- // get(0) is the top-most task
1208
1188
  val topActivityComponentName = tasks[0].taskInfo.topActivity
1209
1189
  return topActivityComponentName?.className?.contains("MainActivity") == true
1210
1190
  }
@@ -1370,6 +1350,7 @@ object CallEngine {
1370
1350
  activeCalls.clear()
1371
1351
  telecomConnections.clear()
1372
1352
  callMetadata.clear()
1353
+ callAnswerStates.clear()
1373
1354
  currentCallId = null
1374
1355
  cleanup()
1375
1356
  lockScreenBypassCallbacks.clear()
@@ -1377,7 +1358,6 @@ object CallEngine {
1377
1358
  cachedEvents.clear()
1378
1359
  isInitialized.set(false)
1379
1360
  appContext = null
1380
- // Reset audio states
1381
1361
  currentActiveCallEndpoint = null
1382
1362
  availableCallEndpoints = emptyList()
1383
1363
  wasManuallySetAudioRoute = false
@@ -8,8 +8,6 @@ class CallManager : HybridCallManagerSpec() {
8
8
 
9
9
  private val TAG = "CallManager"
10
10
 
11
- // Simplified approach - rely on proper Application.onCreate() initialization
12
- // Remove all fallback context access attempts that don't work with Nitro modules
13
11
  private fun ensureInitialized() {
14
12
  if (!CallEngine.isInitialized()) {
15
13
  Log.e(TAG, "CallEngine not initialized! This should not happen if Application.onCreate() was called properly.")
@@ -20,8 +18,6 @@ class CallManager : HybridCallManagerSpec() {
20
18
  }
21
19
  }
22
20
 
23
- // --- All methods must call ensureInitialized() first ---
24
-
25
21
  override fun endCall(callId: String): Unit {
26
22
  Log.d(TAG, "endCall requested for callId: $callId")
27
23
  ensureInitialized()
@@ -83,7 +79,7 @@ class CallManager : HybridCallManagerSpec() {
83
79
  override fun callAnswered(callId: String): Unit {
84
80
  Log.d(TAG, "callAnswered (from JS) requested for callId: $callId")
85
81
  ensureInitialized()
86
- CallEngine.callAnsweredFromJS(callId)
82
+ CallEngine.answerCall(callId, isLocalAnswer = false) // Remote party answered
87
83
  }
88
84
 
89
85
  override fun setOnHold(callId: String, onHold: Boolean): Unit {
@@ -16,14 +16,7 @@ class CallNotificationActionReceiver : BroadcastReceiver() {
16
16
  when (intent.action) {
17
17
  "com.qusaieilouti99.callmanager.ANSWER_CALL" -> {
18
18
  Log.d(TAG, "Answer action received for callId: $callId")
19
- val connection = CallEngine.getTelecomConnection(callId)
20
- if (connection != null) {
21
- connection.onAnswer()
22
- Log.d(TAG, "Call answered via Telecom connection for callId: $callId")
23
- } else {
24
- Log.e(TAG, "No Telecom connection found for callId: $callId. Using direct answer.")
25
- CallEngine.answerCall(callId)
26
- }
19
+ CallEngine.answerCall(callId, isLocalAnswer = true)
27
20
  }
28
21
  "com.qusaieilouti99.callmanager.DECLINE_CALL" -> {
29
22
  Log.d(TAG, "Decline action received for callId: $callId")
@@ -49,7 +49,10 @@ class MyConnection(
49
49
  super.onAnswer(videoState)
50
50
  Log.d(TAG, "Call answered via Telecom for callId: $callId. VideoState: $videoState")
51
51
  setActive()
52
- CallEngine.answerCall(callId)
52
+
53
+ // Retrieve the stored isLocalAnswer state and call coreCallAnswered directly
54
+ val isLocalAnswer = CallEngine.getCallAnswerState(callId) ?: true // default to true if not found
55
+ CallEngine.coreCallAnswered(callId, isLocalAnswer)
53
56
  }
54
57
 
55
58
  override fun onReject() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.184",
3
+ "version": "0.1.185",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",