@qusaieilouti99/call-manager 0.1.145 → 0.1.147

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.
@@ -44,6 +44,10 @@ object CallEngine {
44
44
  fun onCallEnded(callId: String)
45
45
  }
46
46
 
47
+ interface LockScreenBypassCallback {
48
+ fun onLockScreenBypassChanged(shouldBypass: Boolean)
49
+ }
50
+
47
51
  private val callEndListeners = CopyOnWriteArrayList<CallEndListener>()
48
52
  private val mainHandler = Handler(Looper.getMainLooper())
49
53
 
@@ -75,16 +79,18 @@ object CallEngine {
75
79
  private var eventHandler: ((CallEventType, String) -> Unit)? = null
76
80
  private val cachedEvents = mutableListOf<Pair<CallEventType, String>>()
77
81
 
78
- // --- Modern Audio Management State ---
79
82
  private var userSelectedAudioRoute: String? = null
80
83
  private val audioStateLock = Any()
81
84
 
85
+ private var lockScreenBypassActive = false
86
+ private val lockScreenBypassCallbacks = mutableSetOf<LockScreenBypassCallback>()
87
+
82
88
  fun initialize(context: Context) {
83
89
  synchronized(initializationLock) {
84
90
  if (isInitialized.compareAndSet(false, true)) {
85
91
  appContext = context.applicationContext
86
92
  audioManager = appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
87
- registerAudioDeviceCallback() // Register callback on init
93
+ registerAudioDeviceCallback()
88
94
  Log.d(TAG, "CallEngine initialized successfully")
89
95
  }
90
96
  }
@@ -135,6 +141,31 @@ object CallEngine {
135
141
  stopRingtone()
136
142
  }
137
143
 
144
+ fun registerLockScreenBypassCallback(callback: LockScreenBypassCallback) {
145
+ lockScreenBypassCallbacks.add(callback)
146
+ }
147
+
148
+ fun unregisterLockScreenBypassCallback(callback: LockScreenBypassCallback) {
149
+ lockScreenBypassCallbacks.remove(callback)
150
+ }
151
+
152
+ private fun updateLockScreenBypass() {
153
+ val shouldBypass = isCallActive()
154
+ if (lockScreenBypassActive != shouldBypass) {
155
+ lockScreenBypassActive = shouldBypass
156
+ Log.d(TAG, "Lock screen bypass state changed: $lockScreenBypassActive")
157
+ lockScreenBypassCallbacks.forEach { callback ->
158
+ try {
159
+ callback.onLockScreenBypassChanged(shouldBypass)
160
+ } catch (e: Exception) {
161
+ Log.w(TAG, "Error notifying lock screen bypass callback", e)
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ fun isLockScreenBypassActive(): Boolean = lockScreenBypassActive
168
+
138
169
  fun addTelecomConnection(callId: String, connection: Connection) {
139
170
  telecomConnections[callId] = connection
140
171
  }
@@ -192,6 +223,7 @@ object CallEngine {
192
223
  Log.e(TAG, "Failed to report incoming call: ${e.message}", e)
193
224
  endCallInternal(callId)
194
225
  }
226
+ updateLockScreenBypass()
195
227
  }
196
228
 
197
229
  fun startOutgoingCall(
@@ -240,6 +272,7 @@ object CallEngine {
240
272
  Log.e(TAG, "Failed to start outgoing call: ${e.message}", e)
241
273
  endCallInternal(callId)
242
274
  }
275
+ updateLockScreenBypass()
243
276
  }
244
277
 
245
278
  fun startCall(
@@ -278,6 +311,7 @@ object CallEngine {
278
311
  put("callType", callType)
279
312
  put("displayName", targetName)
280
313
  })
314
+ updateLockScreenBypass()
281
315
  }
282
316
 
283
317
  fun callAnsweredFromJS(callId: String) {
@@ -303,11 +337,9 @@ object CallEngine {
303
337
  startForegroundService()
304
338
  keepScreenAwake(true)
305
339
 
306
- // Set initial audio route using the new robust system
307
340
  synchronized(audioStateLock) {
308
- userSelectedAudioRoute = null // Reset any previous user selection
341
+ userSelectedAudioRoute = null
309
342
  }
310
- // Give the audio system a moment to initialize after the call becomes active
311
343
  mainHandler.postDelayed({
312
344
  updateAndApplyAudioRoute()
313
345
  }, 300)
@@ -315,6 +347,7 @@ object CallEngine {
315
347
  if (isLocalAnswer) {
316
348
  emitCallAnsweredWithMetadata(callId)
317
349
  }
350
+ updateLockScreenBypass()
318
351
  }
319
352
 
320
353
  private fun emitCallAnsweredWithMetadata(callId: String) {
@@ -345,6 +378,7 @@ object CallEngine {
345
378
  telecomConnections[callId]?.setOnHold()
346
379
  updateForegroundNotification()
347
380
  emitEvent(CallEventType.CALL_HELD, JSONObject().put("callId", callId))
381
+ updateLockScreenBypass()
348
382
  }
349
383
 
350
384
  fun unholdCall(callId: String) {
@@ -357,6 +391,7 @@ object CallEngine {
357
391
  telecomConnections[callId]?.setActive()
358
392
  updateForegroundNotification()
359
393
  emitEvent(CallEventType.CALL_UNHELD, JSONObject().put("callId", callId))
394
+ updateLockScreenBypass()
360
395
  }
361
396
 
362
397
  fun muteCall(callId: String) {
@@ -390,6 +425,7 @@ object CallEngine {
390
425
  activeCalls.keys.toList().forEach { callId ->
391
426
  endCallInternal(callId)
392
427
  }
428
+ updateLockScreenBypass()
393
429
  }
394
430
 
395
431
  private fun endCallInternal(callId: String) {
@@ -414,6 +450,7 @@ object CallEngine {
414
450
 
415
451
  callEndListeners.forEach { it.onCallEnded(callId) }
416
452
  emitEvent(CallEventType.CALL_ENDED, JSONObject().put("callId", callId))
453
+ updateLockScreenBypass()
417
454
  }
418
455
 
419
456
  fun isCallActive(): Boolean = activeCalls.any {
@@ -423,10 +460,6 @@ object CallEngine {
423
460
  it.value.state == CallState.HELD
424
461
  }
425
462
 
426
- /**
427
- * Sets the user's desired audio route. This will be respected until a
428
- * higher-priority device connects or this route becomes unavailable.
429
- */
430
463
  fun setAudioRoute(route: String) {
431
464
  synchronized(audioStateLock) {
432
465
  Log.d(TAG, "User requested audio route: $route")
@@ -435,12 +468,16 @@ object CallEngine {
435
468
  updateAndApplyAudioRoute()
436
469
  }
437
470
 
438
- /**
439
- * Central function to determine and apply the correct audio route.
440
- * This is the single source of truth for audio routing decisions.
441
- */
442
471
  private fun updateAndApplyAudioRoute() {
443
- val call = activeCalls[currentCallId]
472
+ // *** CRITICAL FIX ***
473
+ // Check for null currentCallId *before* using it as a key.
474
+ val callId = currentCallId
475
+ if (callId == null) {
476
+ Log.d(TAG, "Skipping audio route update: currentCallId is null.")
477
+ return
478
+ }
479
+
480
+ val call = activeCalls[callId]
444
481
  if (call == null || call.state != CallState.ACTIVE) {
445
482
  Log.d(TAG, "Skipping audio route update: No active call.")
446
483
  return
@@ -450,11 +487,9 @@ object CallEngine {
450
487
  val am = audioManager ?: return
451
488
  val availableDevices = getAvailableAudioDevices()
452
489
 
453
- // Determine the target route based on priority
454
490
  val targetRoute = if (userSelectedAudioRoute != null && availableDevices.contains(userSelectedAudioRoute)) {
455
- userSelectedAudioRoute!! // Respect user's choice if available
491
+ userSelectedAudioRoute!!
456
492
  } else {
457
- // Auto-select based on priority
458
493
  when {
459
494
  availableDevices.contains("Bluetooth") -> "Bluetooth"
460
495
  availableDevices.contains("Headset") -> "Headset"
@@ -465,7 +500,6 @@ object CallEngine {
465
500
 
466
501
  Log.d(TAG, "Updating audio route. Available: $availableDevices, User Pref: $userSelectedAudioRoute, Target: $targetRoute")
467
502
 
468
- // Apply the target route
469
503
  am.mode = AudioManager.MODE_IN_COMMUNICATION
470
504
 
471
505
  when (targetRoute) {
@@ -491,7 +525,6 @@ object CallEngine {
491
525
  }
492
526
  }
493
527
 
494
- // After applying, emit the result
495
528
  mainHandler.postDelayed({ emitAudioRouteChanged() }, 100)
496
529
  }
497
530
  }
@@ -499,8 +532,8 @@ object CallEngine {
499
532
  private fun getAvailableAudioDevices(): Set<String> {
500
533
  val am = audioManager ?: return emptySet()
501
534
  val devices = mutableSetOf<String>()
502
- devices.add("Earpiece") // Always available
503
- devices.add("Speaker") // Always available
535
+ devices.add("Earpiece")
536
+ devices.add("Speaker")
504
537
 
505
538
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
506
539
  val audioDevices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
@@ -548,7 +581,6 @@ object CallEngine {
548
581
  private val audioDeviceCallback = object : AudioDeviceCallback() {
549
582
  override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
550
583
  Log.d(TAG, "Audio devices added. Triggering audio route update.")
551
- // A new device was added, reset user preference to allow auto-switch to higher priority device
552
584
  synchronized(audioStateLock) {
553
585
  userSelectedAudioRoute = null
554
586
  }
@@ -556,7 +588,6 @@ object CallEngine {
556
588
  }
557
589
  override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
558
590
  Log.d(TAG, "Audio devices removed. Triggering audio route update.")
559
- // A device was removed, if it was the user's selection, clear it
560
591
  synchronized(audioStateLock) {
561
592
  val removedDeviceTypes = removedDevices.map {
562
593
  when(it.type) {
@@ -581,7 +612,6 @@ object CallEngine {
581
612
  Log.d(TAG, "Performing cleanup")
582
613
  stopForegroundService()
583
614
  keepScreenAwake(false)
584
- // Reset audio state
585
615
  synchronized(audioStateLock) {
586
616
  userSelectedAudioRoute = null
587
617
  }
@@ -601,7 +631,6 @@ object CallEngine {
601
631
  description = "Notifications for incoming calls"
602
632
  setBypassDnd(true)
603
633
  lockscreenVisibility = Notification.VISIBILITY_PUBLIC
604
- // **CRITICAL FIX**: Set sound to null. We will manage the ringtone manually.
605
634
  setSound(null, null)
606
635
  }
607
636
  val manager = context.getSystemService(NotificationManager::class.java)
@@ -612,7 +641,7 @@ object CallEngine {
612
641
  private fun showIncomingCallUI(callId: String, callerName: String, callType: String, callerPicUrl: String?) {
613
642
  val context = requireContext()
614
643
  Log.d(TAG, "Showing incoming call UI for $callId")
615
- createNotificationChannel() // Ensure channel is created with correct settings
644
+ createNotificationChannel()
616
645
 
617
646
  showCallActivityOverlay(context, callId, callerName, callType, callerPicUrl)
618
647
  showStandardNotification(context, callId, callerName, callType, callerPicUrl)
@@ -767,6 +796,9 @@ object CallEngine {
767
796
  )
768
797
  try {
769
798
  context.startActivity(launchIntent)
799
+ Handler(Looper.getMainLooper()).postDelayed({
800
+ updateLockScreenBypass()
801
+ }, 100)
770
802
  } catch (e: Exception) {
771
803
  Log.e(TAG, "Failed to bring app to foreground: ${e.message}")
772
804
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.145",
3
+ "version": "0.1.147",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",