@qusaieilouti99/call-manager 0.1.149 → 0.1.152

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.
@@ -1,5 +1,5 @@
1
1
  package com.margelo.nitro.qusaieilouti99.callmanager
2
-
2
+ import android.telecom.CallAudioState
3
3
  import android.app.ActivityManager
4
4
  import android.app.Notification
5
5
  import android.app.NotificationChannel
@@ -40,8 +40,11 @@ import android.os.VibrationEffect
40
40
  * Core call‐management engine. Manages self-managed telecom calls,
41
41
  * audio routing, UI notifications, etc.
42
42
  *
43
- * NOTE: Volume key silencing is now handled by the system via `Connection.onSilence()`,
44
- * which calls `silenceIncomingCall()` on this object.
43
+ * Audio routing follows Android standards:
44
+ * - Audio calls default to earpiece unless BT/headset connected
45
+ * - Video calls default to speaker unless BT/headset connected
46
+ * - System handles route changes when devices connect/disconnect
47
+ * - Manual route changes are always respected
45
48
  */
46
49
  object CallEngine {
47
50
  private const val TAG = "CallEngine"
@@ -78,20 +81,17 @@ object CallEngine {
78
81
  private val telecomConnections = ConcurrentHashMap<String, Connection>()
79
82
  private val callMetadata = ConcurrentHashMap<String, String>()
80
83
 
81
- // NEW: Track incoming calls to prevent duplicates
82
- private val incomingCallIds = ConcurrentHashMap<String, Long>()
83
-
84
84
  private var currentCallId: String? = null
85
85
  private var canMakeMultipleCalls: Boolean = false
86
- private var lastAudioRoutesInfo: AudioRoutesInfo? = null
87
86
  private var lockScreenBypassActive = false
88
87
  private val lockScreenBypassCallbacks = mutableSetOf<LockScreenBypassCallback>()
89
88
  private var eventHandler: ((CallEventType, String) -> Unit)? = null
90
89
  private val cachedEvents = mutableListOf<Pair<CallEventType, String>>()
91
90
 
92
- // NEW: Track ringtone state to prevent double ringing
93
- private var isCustomRingtoneActive = false
94
- private val ringtoneStateLock = Any()
91
+ // Audio routing state
92
+ private var currentAudioRoute: String = "Earpiece"
93
+ private var wasManuallySet: Boolean = false
94
+ private var callStartTime: Long = 0
95
95
 
96
96
  interface LockScreenBypassCallback {
97
97
  fun onLockScreenBypassChanged(shouldBypass: Boolean)
@@ -118,9 +118,6 @@ object CallEngine {
118
118
  )
119
119
  }
120
120
 
121
- /**
122
- * Get the application context. Returns null if not initialized.
123
- */
124
121
  fun getContext(): Context? = appContext
125
122
 
126
123
  fun setEventHandler(handler: ((CallEventType, String) -> Unit)?) {
@@ -146,16 +143,12 @@ object CallEngine {
146
143
  }
147
144
  }
148
145
 
149
- /**
150
- * NEW: Check if device supports CallStyle notifications
151
- */
152
146
  private fun supportsCallStyleNotifications(): Boolean {
153
147
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return false
154
148
 
155
149
  val manufacturer = Build.MANUFACTURER.lowercase()
156
150
  val brand = Build.BRAND.lowercase()
157
151
 
158
- // Known good manufacturers that support CallStyle properly
159
152
  val supportedManufacturers = setOf(
160
153
  "google", "samsung", "oneplus", "motorola", "sony", "lg", "htc"
161
154
  )
@@ -173,10 +166,6 @@ object CallEngine {
173
166
  return isSupported
174
167
  }
175
168
 
176
- /**
177
- * Silences the incoming call ringtone. This is called by `Connection.onSilence()`
178
- * when the user presses a volume key during ringing.
179
- */
180
169
  fun silenceIncomingCall() {
181
170
  Log.d(TAG, "Silencing incoming call ringtone via Connection.onSilence()")
182
171
  stopRingtone()
@@ -245,19 +234,6 @@ object CallEngine {
245
234
  initialize(context)
246
235
  }
247
236
 
248
- // NEW: Guard against duplicate calls
249
- val currentTime = System.currentTimeMillis()
250
- val lastCallTime = incomingCallIds[callId]
251
- if (lastCallTime != null && (currentTime - lastCallTime) < 5000) {
252
- Log.w(TAG, "Ignoring duplicate incoming call for callId: $callId (last call ${currentTime - lastCallTime}ms ago)")
253
- return
254
- }
255
- incomingCallIds[callId] = currentTime
256
-
257
- // Clean up old entries (older than 30 seconds)
258
- val cutoffTime = currentTime - 30000
259
- incomingCallIds.entries.removeAll { it.value < cutoffTime }
260
-
261
237
  Log.d(TAG, "reportIncomingCall: callId=$callId, type=$callType, name=$displayName")
262
238
  metadata?.let { callMetadata[callId] = it }
263
239
 
@@ -411,6 +387,8 @@ object CallEngine {
411
387
  activeCalls[callId] =
412
388
  CallInfo(callId, callType, targetName, null, CallState.ACTIVE)
413
389
  currentCallId = callId
390
+ callStartTime = System.currentTimeMillis()
391
+ wasManuallySet = false
414
392
  Log.d(TAG, "Call $callId started as ACTIVE")
415
393
 
416
394
  registerPhoneAccount()
@@ -419,9 +397,12 @@ object CallEngine {
419
397
  startForegroundService()
420
398
  keepScreenAwake(true)
421
399
 
422
- // NEW: Improved initial audio route setting with better timing
400
+ // Register audio device callback to handle dynamic device changes
401
+ registerAudioDeviceCallback()
402
+
403
+ // Set initial audio route based on call type and available devices
423
404
  mainHandler.postDelayed({
424
- setInitialAudioRoute(callType, isCallStart = true)
405
+ setInitialAudioRoute(callType)
425
406
  }, 500L)
426
407
 
427
408
  updateLockScreenBypass()
@@ -448,6 +429,8 @@ object CallEngine {
448
429
 
449
430
  activeCalls[callId] = callInfo.copy(state = CallState.ACTIVE)
450
431
  currentCallId = callId
432
+ callStartTime = System.currentTimeMillis()
433
+ wasManuallySet = false
451
434
  Log.d(TAG, "Call $callId set to ACTIVE state")
452
435
 
453
436
  stopRingtone()
@@ -469,20 +452,12 @@ object CallEngine {
469
452
 
470
453
  setAudioMode()
471
454
 
472
- // NEW: Improved initial audio route with longer delay and retry mechanism
473
- mainHandler.postDelayed({
474
- setInitialAudioRoute(callInfo.callType, isCallStart = true)
455
+ // Register audio device callback to handle dynamic device changes
456
+ registerAudioDeviceCallback()
475
457
 
476
- // Retry after additional delay if needed for video calls
477
- if (callInfo.callType == "Video") {
478
- mainHandler.postDelayed({
479
- val currentRoute = getCurrentAudioRoute()
480
- if (currentRoute != "Speaker") {
481
- Log.d(TAG, "Retrying audio route for video call - current: $currentRoute")
482
- setAudioRoute("Speaker")
483
- }
484
- }, 1000L)
485
- }
458
+ // Set initial audio route with proper timing
459
+ mainHandler.postDelayed({
460
+ setInitialAudioRoute(callInfo.callType)
486
461
  }, 800L)
487
462
 
488
463
  if (isLocalAnswer) {
@@ -641,7 +616,6 @@ object CallEngine {
641
616
  activeCalls.clear()
642
617
  telecomConnections.clear()
643
618
  callMetadata.clear()
644
- incomingCallIds.clear() // NEW: Clear duplicate tracking
645
619
  currentCallId = null
646
620
 
647
621
  cleanup()
@@ -658,7 +632,6 @@ object CallEngine {
658
632
 
659
633
  val metadata = callMetadata.remove(callId)
660
634
  activeCalls.remove(callId)
661
- incomingCallIds.remove(callId) // NEW: Clean up duplicate tracking
662
635
 
663
636
  stopRingback()
664
637
  stopRingtone()
@@ -714,146 +687,262 @@ object CallEngine {
714
687
  })
715
688
  }
716
689
 
690
+ // ====== IMPROVED AUDIO ROUTING SYSTEM ======
691
+
717
692
  fun getAudioDevices(): AudioRoutesInfo {
718
693
  val context = requireContext()
719
694
  audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
720
695
  ?: return AudioRoutesInfo(emptyArray(), "Unknown")
721
696
 
722
697
  val devices = mutableSetOf<String>()
723
- devices.add("Speaker")
698
+ var hasWiredHeadset = false
699
+ var hasBluetoothDevice = false
700
+
701
+ // Always available
724
702
  devices.add("Earpiece")
703
+ devices.add("Speaker")
725
704
 
726
705
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
727
- val infos = audioManager?.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
728
- infos?.forEach { d ->
729
- when (d.type) {
706
+ val outputDevices = audioManager?.getDevices(AudioManager.GET_DEVICES_OUTPUTS) ?: emptyArray()
707
+ for (device in outputDevices) {
708
+ when (device.type) {
730
709
  AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
731
- AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> devices.add("Bluetooth")
710
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> {
711
+ if (!hasBluetoothDevice) {
712
+ devices.add("Bluetooth")
713
+ hasBluetoothDevice = true
714
+ }
715
+ }
732
716
  AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
733
- AudioDeviceInfo.TYPE_WIRED_HEADSET -> devices.add("Headset")
717
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
718
+ AudioDeviceInfo.TYPE_USB_HEADSET -> {
719
+ if (!hasWiredHeadset) {
720
+ devices.add("Headset")
721
+ hasWiredHeadset = true
722
+ }
723
+ }
724
+ AudioDeviceInfo.TYPE_BLE_HEADSET -> {
725
+ if (!hasBluetoothDevice) {
726
+ devices.add("Bluetooth")
727
+ hasBluetoothDevice = true
728
+ }
729
+ }
734
730
  }
735
731
  }
736
732
  } else {
733
+ // Fallback for older API levels
737
734
  @Suppress("DEPRECATION")
738
- if (audioManager?.isBluetoothA2dpOn == true || audioManager?.isBluetoothScoOn == true)
735
+ if (audioManager?.isBluetoothA2dpOn == true || audioManager?.isBluetoothScoOn == true) {
739
736
  devices.add("Bluetooth")
737
+ hasBluetoothDevice = true
738
+ }
740
739
  @Suppress("DEPRECATION")
741
- if (audioManager?.isWiredHeadsetOn == true) devices.add("Headset")
740
+ if (audioManager?.isWiredHeadsetOn == true) {
741
+ devices.add("Headset")
742
+ hasWiredHeadset = true
743
+ }
742
744
  }
743
745
 
744
746
  val current = getCurrentAudioRoute()
745
747
  Log.d(TAG, "Available audio devices: ${devices.toList()}, current: $current")
746
748
 
747
- // Convert strings to StringHolder objects
748
749
  val deviceHolders = devices.map { StringHolder(it) }.toTypedArray()
749
- lastAudioRoutesInfo = AudioRoutesInfo(deviceHolders, current)
750
750
  return AudioRoutesInfo(deviceHolders, current)
751
751
  }
752
752
 
753
- fun setAudioRoute(route: String) {
754
- Log.d(TAG, "setAudioRoute called: $route")
753
+ // NEW: Handle telecom audio route changes
754
+ fun onTelecomAudioRouteChanged(callId: String, audioState: CallAudioState) {
755
+ Log.d(TAG, "Telecom audio route changed for $callId: route=${audioState.route}")
756
+
757
+ val routeString = when (audioState.route) {
758
+ CallAudioState.ROUTE_EARPIECE -> "Earpiece"
759
+ CallAudioState.ROUTE_SPEAKER -> "Speaker"
760
+ CallAudioState.ROUTE_BLUETOOTH -> "Bluetooth"
761
+ CallAudioState.ROUTE_WIRED_HEADSET -> "Headset"
762
+ else -> "Unknown"
763
+ }
764
+
765
+ Log.d(TAG, "Emitting AUDIO_ROUTE_CHANGED: currentRoute=$routeString")
766
+ emitAudioRouteChanged(routeString)
767
+ }
768
+
769
+ // NEW: Set initial audio route using telecom
770
+ fun setInitialAudioRouteForCall(callId: String, callType: String) {
771
+ Log.d(TAG, "Setting initial audio route for $callId, type: $callType")
755
772
 
773
+ // Determine the desired route
774
+ val desiredRoute = when {
775
+ isBluetoothDeviceConnected() -> CallAudioState.ROUTE_BLUETOOTH
776
+ isWiredHeadsetConnected() -> CallAudioState.ROUTE_WIRED_HEADSET
777
+ callType.equals("Video", ignoreCase = true) -> CallAudioState.ROUTE_SPEAKER
778
+ else -> CallAudioState.ROUTE_EARPIECE
779
+ }
780
+
781
+ // Use telecom connection to set the route
782
+ telecomConnections[callId]?.let { connection ->
783
+ if (connection is MyConnection) {
784
+ mainHandler.postDelayed({
785
+ connection.setTelecomAudioRoute(desiredRoute)
786
+ Log.d(TAG, "Set initial telecom audio route to: $desiredRoute")
787
+ }, 200)
788
+ }
789
+ }
790
+ }
791
+
792
+ // UPDATED: Use telecom for manual route changes
793
+ fun setAudioRoute(route: String) {
794
+ Log.d(TAG, "setAudioRoute called: $route (manual)")
795
+ wasManuallySet = true
796
+
797
+ val telecomRoute = when (route) {
798
+ "Speaker" -> CallAudioState.ROUTE_SPEAKER
799
+ "Earpiece" -> CallAudioState.ROUTE_EARPIECE
800
+ "Bluetooth" -> CallAudioState.ROUTE_BLUETOOTH
801
+ "Headset" -> CallAudioState.ROUTE_WIRED_HEADSET
802
+ else -> {
803
+ Log.w(TAG, "Unknown audio route: $route")
804
+ return
805
+ }
806
+ }
807
+
808
+ // Set route through active telecom connection
809
+ currentCallId?.let { callId ->
810
+ telecomConnections[callId]?.let { connection ->
811
+ if (connection is MyConnection) {
812
+ connection.setTelecomAudioRoute(telecomRoute)
813
+ Log.d(TAG, "Set telecom audio route to: $telecomRoute for $route")
814
+ }
815
+ }
816
+ }
817
+ }
818
+
819
+ private fun applyAudioRoute(route: String) {
756
820
  val ctx = requireContext()
757
821
  if (audioManager == null) {
758
822
  audioManager = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
759
823
  }
760
824
  val am = audioManager!!
761
825
 
826
+ // Ensure we're in the correct audio mode
762
827
  if (am.mode != AudioManager.MODE_IN_COMMUNICATION) {
763
828
  am.mode = AudioManager.MODE_IN_COMMUNICATION
764
829
  }
765
830
 
831
+ val previousRoute = currentAudioRoute
832
+
766
833
  when (route) {
767
834
  "Speaker" -> {
768
835
  am.isSpeakerphoneOn = true
769
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && am.isBluetoothScoOn) {
836
+ if (am.isBluetoothScoOn) {
770
837
  am.stopBluetoothSco()
771
838
  am.isBluetoothScoOn = false
772
839
  }
773
- Log.d(TAG, "Audio routed to SPEAKER")
840
+ currentAudioRoute = "Speaker"
841
+ Log.d(TAG, "Audio route set to SPEAKER")
774
842
  }
775
843
  "Earpiece" -> {
776
844
  am.isSpeakerphoneOn = false
777
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && am.isBluetoothScoOn) {
845
+ if (am.isBluetoothScoOn) {
778
846
  am.stopBluetoothSco()
779
847
  am.isBluetoothScoOn = false
780
848
  }
781
- Log.d(TAG, "Audio routed to EARPIECE")
849
+ currentAudioRoute = "Earpiece"
850
+ Log.d(TAG, "Audio route set to EARPIECE")
782
851
  }
783
852
  "Bluetooth" -> {
784
853
  am.isSpeakerphoneOn = false
785
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
854
+ if (!am.isBluetoothScoOn) {
786
855
  am.startBluetoothSco()
787
856
  am.isBluetoothScoOn = true
788
- Log.d(TAG, "Audio routed to BLUETOOTH")
789
- } else {
790
- Log.w(TAG, "Bluetooth SCO not supported on this OS version")
791
857
  }
858
+ currentAudioRoute = "Bluetooth"
859
+ Log.d(TAG, "Audio route set to BLUETOOTH")
792
860
  }
793
861
  "Headset" -> {
794
862
  am.isSpeakerphoneOn = false
795
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && am.isBluetoothScoOn) {
863
+ if (am.isBluetoothScoOn) {
796
864
  am.stopBluetoothSco()
797
865
  am.isBluetoothScoOn = false
798
866
  }
799
- Log.d(TAG, "Audio routed to HEADSET")
867
+ // For wired headsets, the system automatically routes audio when connected
868
+ currentAudioRoute = "Headset"
869
+ Log.d(TAG, "Audio route set to HEADSET")
800
870
  }
801
871
  else -> {
802
872
  Log.w(TAG, "Unknown audio route: $route")
803
873
  return
804
874
  }
805
875
  }
806
- emitAudioRouteChanged()
876
+
877
+ // Only emit event if route actually changed
878
+ if (currentAudioRoute != previousRoute) {
879
+ emitAudioRouteChanged(currentAudioRoute)
880
+ }
807
881
  }
808
882
 
809
883
  private fun getCurrentAudioRoute(): String {
884
+ val am = audioManager ?: return "Unknown"
885
+
886
+ // Check in order of priority: Bluetooth -> Headset -> Speaker -> Earpiece
810
887
  return when {
811
- audioManager?.isBluetoothScoOn == true -> "Bluetooth"
812
- audioManager?.isSpeakerphoneOn == true -> "Speaker"
813
- audioManager?.isWiredHeadsetOn == true -> "Headset"
888
+ am.isBluetoothScoOn -> "Bluetooth"
889
+ isWiredHeadsetConnected() -> "Headset"
890
+ am.isSpeakerphoneOn -> "Speaker"
814
891
  else -> "Earpiece"
815
892
  }
816
893
  }
817
894
 
818
- // NEW: Improved initial audio route setting
819
- private fun setInitialAudioRoute(callType: String, isCallStart: Boolean = false) {
820
- val avail = getAudioDevices()
821
- // Extract string values for comparison
822
- val deviceStrings = avail.devices.map { it.value }
895
+ private fun isWiredHeadsetConnected(): Boolean {
896
+ val am = audioManager ?: return false
823
897
 
824
- val defaultRoute = when {
825
- deviceStrings.contains("Bluetooth") -> "Bluetooth"
826
- deviceStrings.contains("Headset") -> "Headset"
827
- callType == "Video" -> "Speaker"
828
- else -> "Earpiece"
898
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
899
+ val devices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
900
+ return devices.any { device ->
901
+ device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
902
+ device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
903
+ device.type == AudioDeviceInfo.TYPE_USB_HEADSET
904
+ }
905
+ } else {
906
+ @Suppress("DEPRECATION")
907
+ return am.isWiredHeadsetOn
829
908
  }
909
+ }
830
910
 
831
- Log.d(TAG, "Setting initial audio route: $defaultRoute for call type: $callType (isCallStart: $isCallStart)")
832
-
833
- // For call start, ensure audio mode is properly set first
834
- if (isCallStart) {
835
- setAudioMode()
911
+ private fun isBluetoothDeviceConnected(): Boolean {
912
+ val am = audioManager ?: return false
836
913
 
837
- // Additional delay for audio system to be ready
838
- if (callType == "Video") {
839
- mainHandler.postDelayed({
840
- setAudioRoute(defaultRoute)
841
-
842
- // Force speaker for video calls with additional verification
843
- mainHandler.postDelayed({
844
- val currentRoute = getCurrentAudioRoute()
845
- if (currentRoute != "Speaker" && !deviceStrings.contains("Bluetooth") && !deviceStrings.contains("Headset")) {
846
- Log.d(TAG, "Forcing speaker for video call - current route was: $currentRoute")
847
- setAudioRoute("Speaker")
848
- }
849
- }, 300L)
850
- }, 200L)
851
- } else {
852
- setAudioRoute(defaultRoute)
914
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
915
+ val devices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
916
+ return devices.any { device ->
917
+ device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
918
+ device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO ||
919
+ device.type == AudioDeviceInfo.TYPE_BLE_HEADSET
853
920
  }
854
921
  } else {
855
- setAudioRoute(defaultRoute)
922
+ @Suppress("DEPRECATION")
923
+ return am.isBluetoothA2dpOn || am.isBluetoothScoOn
924
+ }
925
+ }
926
+
927
+ private fun setInitialAudioRoute(callType: String) {
928
+ Log.d(TAG, "Setting initial audio route for call type: $callType")
929
+
930
+ // Don't override if user manually set a route
931
+ if (wasManuallySet) {
932
+ Log.d(TAG, "Audio route was manually set, skipping initial route")
933
+ return
856
934
  }
935
+
936
+ // Determine default route based on Android standards
937
+ val defaultRoute = when {
938
+ isBluetoothDeviceConnected() -> "Bluetooth"
939
+ isWiredHeadsetConnected() -> "Headset"
940
+ callType.equals("Video", ignoreCase = true) -> "Speaker"
941
+ else -> "Earpiece" // Default for audio calls
942
+ }
943
+
944
+ Log.d(TAG, "Setting initial audio route to: $defaultRoute")
945
+ applyAudioRoute(defaultRoute)
857
946
  }
858
947
 
859
948
  private fun setAudioMode() {
@@ -863,40 +952,124 @@ object CallEngine {
863
952
 
864
953
  private fun resetAudioMode() {
865
954
  if (activeCalls.isEmpty()) {
866
- audioManager?.mode = AudioManager.MODE_NORMAL
867
- audioManager?.stopBluetoothSco()
868
- audioManager?.isBluetoothScoOn = false
869
- audioManager?.isSpeakerphoneOn = false
955
+ audioManager?.let { am ->
956
+ am.mode = AudioManager.MODE_NORMAL
957
+ if (am.isBluetoothScoOn) {
958
+ am.stopBluetoothSco()
959
+ am.isBluetoothScoOn = false
960
+ }
961
+ am.isSpeakerphoneOn = false
962
+ }
963
+ currentAudioRoute = "Earpiece"
964
+ wasManuallySet = false
965
+ unregisterAudioDeviceCallback()
870
966
  Log.d(TAG, "Audio mode reset to MODE_NORMAL")
871
967
  }
872
968
  }
873
969
 
874
- private fun emitAudioRouteChanged() {
875
- val info = getAudioDevices()
876
- // Extract string values from StringHolder objects
877
- val deviceStrings = info.devices.map { it.value }
878
- val payload = JSONObject().apply {
879
- put("devices", JSONArray(deviceStrings))
880
- put("currentRoute", info.currentRoute)
881
- }
882
- emitEvent(CallEventType.AUDIO_ROUTE_CHANGED, payload)
883
- Log.d(TAG, "Audio route changed: ${info.currentRoute}, available: $deviceStrings")
970
+ // UPDATED: Fix the method signature
971
+ private fun emitAudioRouteChanged(currentRoute: String) {
972
+ val info = getAudioDevices()
973
+ val deviceStrings = info.devices.map { it.value }
974
+ val payload = JSONObject().apply {
975
+ put("devices", JSONArray(deviceStrings))
976
+ put("currentRoute", currentRoute)
977
+ }
978
+ emitEvent(CallEventType.AUDIO_ROUTE_CHANGED, payload)
979
+ Log.d(TAG, "Audio route changed: $currentRoute, available: $deviceStrings")
884
980
  }
885
981
 
886
982
  private val audioDeviceCallback = object : AudioDeviceCallback() {
887
983
  override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
888
984
  Log.d(TAG, "Audio devices added")
889
- emitAudioDevicesChanged()
985
+ handleAudioDeviceChange(addedDevices, true)
890
986
  }
987
+
891
988
  override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
892
989
  Log.d(TAG, "Audio devices removed")
893
- emitAudioDevicesChanged()
990
+ handleAudioDeviceChange(removedDevices, false)
894
991
  }
895
992
  }
896
993
 
994
+ private fun handleAudioDeviceChange(devices: Array<out AudioDeviceInfo>?, isAdded: Boolean) {
995
+ if (devices == null || !isCallActive()) return
996
+
997
+ val context = requireContext()
998
+ val currentCallInfo = getCurrentActiveCall()
999
+ if (currentCallInfo == null) {
1000
+ Log.d(TAG, "No active call, ignoring device change")
1001
+ return
1002
+ }
1003
+
1004
+ val relevantDevices = devices.filter { device ->
1005
+ device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
1006
+ device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO ||
1007
+ device.type == AudioDeviceInfo.TYPE_BLE_HEADSET ||
1008
+ device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
1009
+ device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
1010
+ device.type == AudioDeviceInfo.TYPE_USB_HEADSET
1011
+ }
1012
+
1013
+ if (relevantDevices.isEmpty()) {
1014
+ Log.d(TAG, "No relevant devices in change event")
1015
+ return
1016
+ }
1017
+
1018
+ Log.d(TAG, "Relevant device change detected. Added: $isAdded, wasManuallySet: $wasManuallySet")
1019
+
1020
+ if (isAdded && !wasManuallySet) {
1021
+ // Device connected - switch to it automatically if user hasn't manually set route
1022
+ val deviceType = relevantDevices.first().type
1023
+ val newRoute = when (deviceType) {
1024
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
1025
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
1026
+ AudioDeviceInfo.TYPE_BLE_HEADSET -> "Bluetooth"
1027
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
1028
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
1029
+ AudioDeviceInfo.TYPE_USB_HEADSET -> "Headset"
1030
+ else -> null
1031
+ }
1032
+
1033
+ if (newRoute != null && newRoute != currentAudioRoute) {
1034
+ Log.d(TAG, "Auto-switching to newly connected device: $newRoute")
1035
+ // Add slight delay to ensure device is ready
1036
+ mainHandler.postDelayed({
1037
+ applyAudioRoute(newRoute)
1038
+ }, 300)
1039
+ }
1040
+ } else if (!isAdded) {
1041
+ // Device disconnected - fall back to appropriate route
1042
+ val disconnectedType = relevantDevices.first().type
1043
+ val wasCurrentRoute = when (disconnectedType) {
1044
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
1045
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
1046
+ AudioDeviceInfo.TYPE_BLE_HEADSET -> currentAudioRoute == "Bluetooth"
1047
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
1048
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
1049
+ AudioDeviceInfo.TYPE_USB_HEADSET -> currentAudioRoute == "Headset"
1050
+ else -> false
1051
+ }
1052
+
1053
+ if (wasCurrentRoute) {
1054
+ Log.d(TAG, "Current audio device disconnected, falling back")
1055
+ // Reset manual flag since the manually selected device is gone
1056
+ wasManuallySet = false
1057
+ mainHandler.postDelayed({
1058
+ setInitialAudioRoute(currentCallInfo.callType)
1059
+ }, 300)
1060
+ }
1061
+ }
1062
+
1063
+ // Always emit devices changed event
1064
+ emitAudioDevicesChanged()
1065
+ }
1066
+
1067
+ private fun getCurrentActiveCall(): CallInfo? {
1068
+ return activeCalls.values.find { it.state == CallState.ACTIVE }
1069
+ }
1070
+
897
1071
  private fun emitAudioDevicesChanged() {
898
1072
  val info = getAudioDevices()
899
- // Extract string values from StringHolder objects
900
1073
  val deviceStrings = info.devices.map { it.value }
901
1074
  val payload = JSONObject().apply {
902
1075
  put("devices", JSONArray(deviceStrings))
@@ -907,17 +1080,29 @@ object CallEngine {
907
1080
  }
908
1081
 
909
1082
  fun registerAudioDeviceCallback() {
910
- val context = requireContext()
911
- audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
912
- audioManager?.registerAudioDeviceCallback(audioDeviceCallback, null)
1083
+ if (isCallActive()) {
1084
+ val context = requireContext()
1085
+ audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
1086
+ try {
1087
+ audioManager?.registerAudioDeviceCallback(audioDeviceCallback, null)
1088
+ Log.d(TAG, "Audio device callback registered")
1089
+ } catch (e: Exception) {
1090
+ Log.w(TAG, "Failed to register audio device callback: ${e.message}")
1091
+ }
1092
+ }
913
1093
  }
914
1094
 
915
1095
  fun unregisterAudioDeviceCallback() {
916
- val context = requireContext()
917
- audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
918
- audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
1096
+ try {
1097
+ audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
1098
+ Log.d(TAG, "Audio device callback unregistered")
1099
+ } catch (e: Exception) {
1100
+ Log.w(TAG, "Failed to unregister audio device callback: ${e.message}")
1101
+ }
919
1102
  }
920
1103
 
1104
+ // ====== END AUDIO ROUTING SYSTEM ======
1105
+
921
1106
  fun keepScreenAwake(keepAwake: Boolean) {
922
1107
  val context = requireContext()
923
1108
  val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
@@ -958,7 +1143,6 @@ object CallEngine {
958
1143
 
959
1144
  private fun rejectIncomingCallCollision(callId: String, reason: String) {
960
1145
  callMetadata.remove(callId)
961
- incomingCallIds.remove(callId) // NEW: Clean up duplicate tracking
962
1146
  emitEvent(CallEventType.CALL_REJECTED, JSONObject().apply {
963
1147
  put("callId", callId)
964
1148
  put("reason", reason)
@@ -980,7 +1164,6 @@ object CallEngine {
980
1164
  channel.setBypassDnd(true)
981
1165
  channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
982
1166
 
983
- // NEW: Improved sound handling to prevent double ringing
984
1167
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
985
1168
  channel.setSound(
986
1169
  RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
@@ -990,7 +1173,6 @@ object CallEngine {
990
1173
  .build()
991
1174
  )
992
1175
  } else {
993
- // For API 31+, disable notification sound to prevent conflicts with custom ringtone
994
1176
  channel.setSound(null, null)
995
1177
  channel.importance = NotificationManager.IMPORTANCE_HIGH
996
1178
  }
@@ -1013,8 +1195,6 @@ object CallEngine {
1013
1195
  Log.d(TAG, "Device is unlocked and supports CallStyle - using enhanced notification")
1014
1196
  showStandardNotification(context, callId, callerName, callType, callerPicUrl)
1015
1197
  }
1016
-
1017
- // NEW: Improved ringtone handling to prevent double ringing
1018
1198
  playRingtone()
1019
1199
  }
1020
1200
 
@@ -1258,73 +1438,43 @@ object CallEngine {
1258
1438
  )
1259
1439
  }
1260
1440
 
1261
- // NEW: Improved ringtone handling to prevent double ringing
1262
1441
  private fun playRingtone() {
1263
- synchronized(ringtoneStateLock) {
1264
- if (isCustomRingtoneActive) {
1265
- Log.d(TAG, "Custom ringtone already active, skipping")
1266
- return
1267
- }
1268
-
1269
- val context = requireContext()
1270
- audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
1271
-
1272
- // Only set ringtone mode if not already in communication mode
1273
- val currentMode = audioManager?.mode ?: AudioManager.MODE_NORMAL
1274
- if (currentMode != AudioManager.MODE_IN_COMMUNICATION) {
1275
- audioManager?.mode = AudioManager.MODE_RINGTONE
1276
- }
1442
+ val context = requireContext()
1443
+ audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
1444
+ audioManager?.mode = AudioManager.MODE_RINGTONE
1277
1445
 
1278
- vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
1279
- vibrator?.let { v ->
1280
- val pattern = longArrayOf(0L, 500L, 500L)
1281
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1282
- v.vibrate(VibrationEffect.createWaveform(pattern, 0))
1283
- } else {
1284
- @Suppress("DEPRECATION")
1285
- v.vibrate(pattern, 0)
1286
- }
1446
+ vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
1447
+ vibrator?.let { v ->
1448
+ val pattern = longArrayOf(0L, 500L, 500L)
1449
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
1450
+ v.vibrate(VibrationEffect.createWaveform(pattern, 0))
1451
+ } else {
1452
+ @Suppress("DEPRECATION")
1453
+ v.vibrate(pattern, 0)
1287
1454
  }
1455
+ }
1288
1456
 
1289
- try {
1290
- // Check if system ringtone is already playing for this call (API 31+)
1291
- val shouldPlayCustomRingtone = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
1292
- // For newer APIs, be more cautious about playing custom ringtone
1293
- // if CallStyle notification is being used
1294
- !supportsCallStyleNotifications()
1295
- } else {
1296
- true
1297
- }
1298
-
1299
- if (shouldPlayCustomRingtone) {
1300
- val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
1301
- ringtone = RingtoneManager.getRingtone(context, uri)
1302
- ringtone?.play()
1303
- isCustomRingtoneActive = true
1304
- Log.d(TAG, "Custom ringtone started playing")
1305
- } else {
1306
- Log.d(TAG, "Skipping custom ringtone - system should handle it")
1307
- }
1308
- } catch (e: Exception) {
1309
- Log.e(TAG, "Failed to play ringtone", e)
1310
- }
1457
+ try {
1458
+ val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
1459
+ ringtone = RingtoneManager.getRingtone(context, uri)
1460
+ ringtone?.play()
1461
+ Log.d(TAG, "Ringtone started playing")
1462
+ } catch (e: Exception) {
1463
+ Log.e(TAG, "Failed to play ringtone", e)
1311
1464
  }
1312
1465
  }
1313
1466
 
1314
1467
  fun stopRingtone() {
1315
- synchronized(ringtoneStateLock) {
1316
- try {
1317
- ringtone?.stop()
1318
- isCustomRingtoneActive = false
1319
- Log.d(TAG, "Ringtone stopped")
1320
- } catch (e: Exception) {
1321
- Log.e(TAG, "Error stopping ringtone", e)
1322
- }
1323
- ringtone = null
1324
-
1325
- vibrator?.cancel()
1326
- vibrator = null
1468
+ try {
1469
+ ringtone?.stop()
1470
+ Log.d(TAG, "Ringtone stopped")
1471
+ } catch (e: Exception) {
1472
+ Log.e(TAG, "Error stopping ringtone", e)
1327
1473
  }
1474
+ ringtone = null
1475
+
1476
+ vibrator?.cancel()
1477
+ vibrator = null
1328
1478
  }
1329
1479
 
1330
1480
  private fun startRingback() {
@@ -1360,9 +1510,6 @@ object CallEngine {
1360
1510
  stopForegroundService()
1361
1511
  keepScreenAwake(false)
1362
1512
  resetAudioMode()
1363
- synchronized(ringtoneStateLock) {
1364
- isCustomRingtoneActive = false
1365
- }
1366
1513
  }
1367
1514
 
1368
1515
  fun onApplicationTerminate() {
@@ -1376,7 +1523,6 @@ object CallEngine {
1376
1523
  activeCalls.clear()
1377
1524
  telecomConnections.clear()
1378
1525
  callMetadata.clear()
1379
- incomingCallIds.clear() // NEW: Clear duplicate tracking
1380
1526
  currentCallId = null
1381
1527
  cleanup()
1382
1528
  lockScreenBypassCallbacks.clear()
@@ -40,16 +40,11 @@ class MyConnection(
40
40
  Log.d(TAG, "MyConnection for callId $callId created and added to CallEngine. Type: $callType")
41
41
  }
42
42
 
43
- // --- THIS IS THE MISSING PIECE ---
44
- /**
45
- * Called by the system when the user presses a volume key during ringing.
46
- */
47
43
  override fun onSilence() {
48
44
  super.onSilence()
49
45
  Log.d(TAG, "onSilence called by system for callId: $callId. Silencing ringtone.")
50
46
  CallEngine.silenceIncomingCall()
51
47
  }
52
- // ---------------------------------
53
48
 
54
49
  override fun onAnswer() {
55
50
  Log.d(TAG, "Call answered via Telecom for callId: $callId")
@@ -97,6 +92,8 @@ class MyConnection(
97
92
 
98
93
  if (lastAudioState == null || lastAudioState!!.route != state.route) {
99
94
  Log.d(TAG, "System audio route changed for callId: $callId. Telecom route: ${state.route}")
95
+ // Notify CallEngine about the actual route change
96
+ CallEngine.onTelecomAudioRouteChanged(callId, state)
100
97
  }
101
98
 
102
99
  lastAudioState = state
@@ -134,6 +131,8 @@ class MyConnection(
134
131
  }
135
132
  STATE_ACTIVE -> {
136
133
  Log.d(TAG, "Connection is now active for callId: $callId")
134
+ // Set initial audio route when call becomes active
135
+ CallEngine.setInitialAudioRouteForCall(callId, callType)
137
136
  }
138
137
  STATE_DISCONNECTED -> {
139
138
  Log.d(TAG, "Connection is now disconnected for callId: $callId")
@@ -141,4 +140,10 @@ class MyConnection(
141
140
  }
142
141
  }
143
142
  }
143
+
144
+ // NEW: Method to set audio route through telecom
145
+ fun setTelecomAudioRoute(route: Int) {
146
+ Log.d(TAG, "Setting telecom audio route to: $route for callId: $callId")
147
+ setAudioRoute(route)
148
+ }
144
149
  }
@@ -77,6 +77,23 @@ public class CallManager: HybridCallManagerSpec {
77
77
  )
78
78
  }
79
79
 
80
+ public func reportIncomingCall(callId: String,
81
+ callType: String,
82
+ targetName: String,
83
+ metadata: String?) throws
84
+ {
85
+ logger.info("🎯 startOutgoingCall ▶ js → native: \(callId), type=\(callType)")
86
+ if let m = metadata { logger.debug("🎯 metadata.len=\(m.count)") }
87
+ CallEngine.shared.reportIncomingCall(
88
+ callId: callId,
89
+ callType: callType,
90
+ targetName: targetName,
91
+ nil,
92
+ metadata: metadata
93
+ )
94
+ }
95
+
96
+
80
97
  public func startCall(callId: String,
81
98
  callType: String,
82
99
  targetName: String,
@@ -1 +1 @@
1
- {"version":3,"names":["NitroModules","CallManagerHybridObject","createHybridObject"],"sourceRoot":"../../src","sources":["CallManager.nitro.ts"],"mappings":";;AAAA;AACA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAI5E;;AAKA;;AA2CA,OAAO,MAAMC,uBAAuB,GAClCD,YAAY,CAACE,kBAAkB,CAAc,aAAa,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["NitroModules","CallManagerHybridObject","createHybridObject"],"sourceRoot":"../../src","sources":["CallManager.nitro.ts"],"mappings":";;AAAA;AACA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAG5E;;AAKA;;AAyDA,OAAO,MAAMC,uBAAuB,GAClCD,YAAY,CAACE,kBAAkB,CAAc,aAAa,CAAC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"CallManager.nitro.d.ts","sourceRoot":"","sources":["../../../src/CallManager.nitro.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIrD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAY,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IAEpF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,IAAI,IAAI,CAAC;IACxB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAGnC,eAAe,IAAI,eAAe,CAAC;IACnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAE1C,WAAW,IAAI,IAAI,CAAC;IACpB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAGvE,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGlG,WAAW,CAET,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACxD,MAAM,IAAI,CAAC;IAEd,yBAAyB,CAEvB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAClC,MAAM,IAAI,CAAC;IAGd,aAAa,IAAI,OAAO,CAAC;CAC1B;AAGD,eAAO,MAAM,uBAAuB,aACyB,CAAC"}
1
+ {"version":3,"file":"CallManager.nitro.d.ts","sourceRoot":"","sources":["../../../src/CallManager.nitro.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IAEzD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,IAAI,IAAI,CAAC;IACxB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAGnC,eAAe,IAAI,eAAe,CAAC;IACnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAE1C,WAAW,IAAI,IAAI,CAAC;IACpB,iBAAiB,CACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CACP,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IACR,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAGvE,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAAC;IAGR,WAAW,CAET,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GACxD,MAAM,IAAI,CAAC;IAEd,yBAAyB,CAEvB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAClC,MAAM,IAAI,CAAC;IAEd,aAAa,IAAI,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,uBAAuB,aACyB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.149",
3
+ "version": "0.1.152",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -62,29 +62,35 @@
62
62
  "registry": "https://registry.npmjs.org/"
63
63
  },
64
64
  "devDependencies": {
65
- "@commitlint/config-conventional": "^19.8.1",
66
- "@eslint/compat": "^1.3.1",
67
- "@eslint/eslintrc": "^1.4.1",
68
- "@eslint/js": "^10.0.0",
69
- "@evilmartians/lefthook": "^1.12.2",
65
+ "@commitlint/config-conventional": "^19.6.0",
66
+ "@eslint/compat": "^1.2.7",
67
+ "@eslint/eslintrc": "^3.3.0",
68
+ "@eslint/js": "^9.22.0",
69
+ "@evilmartians/lefthook": "^1.5.0",
70
70
  "@react-native/babel-preset": "0.79.2",
71
- "@react-native/eslint-config": "^0.79.2",
71
+ "@react-native/eslint-config": "^0.78.0",
72
+ "@release-it/conventional-changelog": "^9.0.2",
72
73
  "@types/jest": "^29.5.5",
73
74
  "@types/react": "^19.0.0",
74
- "commitlint": "^19.8.1",
75
+ "@typescript-eslint/eslint-plugin": "^8.39.0",
76
+ "commitlint": "^19.6.1",
75
77
  "del-cli": "^5.1.0",
76
- "eslint": "^9.32.0",
77
- "eslint-config-prettier": "^10.1.8",
78
- "eslint-plugin-prettier": "^5.5.3",
78
+ "eslint": "^9.22.0",
79
+ "eslint-config-prettier": "^10.1.1",
80
+ "eslint-plugin-ft-flow": "^3.0.11",
81
+ "eslint-plugin-jest": "^29.0.1",
82
+ "eslint-plugin-prettier": "^5.2.3",
83
+ "eslint-plugin-react-hooks": "^5.2.0",
84
+ "eslint-plugin-react-native": "^5.0.0",
79
85
  "jest": "^29.7.0",
80
86
  "nitro-codegen": "*",
81
- "prettier": "^3.6.2",
87
+ "prettier": "^3.0.3",
82
88
  "react": "19.0.0",
83
89
  "react-native": "0.79.2",
84
- "react-native-builder-bob": "^0.40.13",
90
+ "react-native-builder-bob": "^0.40.8",
85
91
  "react-native-nitro-modules": "*",
86
- "release-it": "^19.0.4",
87
- "turbo": "^1.13.4",
92
+ "release-it": "^17.10.0",
93
+ "turbo": "^1.10.7",
88
94
  "typescript": "^5.9.2"
89
95
  },
90
96
  "peerDependencies": {
@@ -2,7 +2,6 @@
2
2
  import { type HybridObject, NitroModules } from 'react-native-nitro-modules';
3
3
  import type { CallEventType } from './CallEventType';
4
4
 
5
-
6
5
  // This is workaround for a swift compiler bug, its preventing us from using string[].
7
6
  export interface StringHolder {
8
7
  value: string;
@@ -14,7 +13,8 @@ export interface AudioRoutesInfo {
14
13
  currentRoute: string; // Currently active audio route (e.g., "Speaker", "Earpiece", "Bluetooth", "Headset", "Unknown")
15
14
  }
16
15
 
17
- export interface CallManager extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
16
+ export interface CallManager
17
+ extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
18
18
  // Call control
19
19
  endCall(callId: string): void;
20
20
  silenceRingtone(): void;
@@ -26,14 +26,29 @@ export interface CallManager extends HybridObject<{ ios: 'swift'; android: 'kotl
26
26
  keepScreenAwake(keepAwake: boolean): void;
27
27
 
28
28
  endAllCalls(): void;
29
- startOutgoingCall(callId: string, callType: string, targetName: string, metadata?: string): void;
30
- startCall(callId: string, callType: string, targetName: string, metadata?: string): void;
29
+ startOutgoingCall(
30
+ callId: string,
31
+ callType: string,
32
+ targetName: string,
33
+ metadata?: string
34
+ ): void;
35
+ startCall(
36
+ callId: string,
37
+ callType: string,
38
+ targetName: string,
39
+ metadata?: string
40
+ ): void;
31
41
  setOnHold(callId: string, onHold: boolean): void;
32
42
  setMuted(callId: string, muted: boolean): void;
33
43
  updateDisplayCallInformation(callId: string, callerName: string): void;
34
44
 
35
45
  // added just new
36
- reportIncomingCall(callId: string, callType: string, targetName: string, metadata?: string): void;
46
+ reportIncomingCall(
47
+ callId: string,
48
+ callType: string,
49
+ targetName: string,
50
+ metadata?: string
51
+ ): void;
37
52
 
38
53
  // Event emitter: addListener returns a remove function
39
54
  addListener(
@@ -46,10 +61,8 @@ export interface CallManager extends HybridObject<{ ios: 'swift'; android: 'kotl
46
61
  listener: (payload: string) => void
47
62
  ): () => void;
48
63
 
49
-
50
- hasActiveCall() :boolean; // if there is an active call, no matter ringing, incoming, outgoing, whatever
64
+ hasActiveCall(): boolean; // if there is an active call, no matter ringing, incoming, outgoing, whatever
51
65
  }
52
66
 
53
-
54
67
  export const CallManagerHybridObject =
55
68
  NitroModules.createHybridObject<CallManager>('CallManager');