@qusaieilouti99/call-manager 0.1.42 → 0.1.44

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,4 +1,5 @@
1
1
  package com.margelo.nitro.qusaieilouti99.callmanager
2
+
2
3
  import android.app.Activity
3
4
  import android.content.Intent
4
5
  import android.os.Build
@@ -28,18 +29,28 @@ class CallActivity : Activity() {
28
29
  override fun onCreate(savedInstanceState: Bundle?) {
29
30
  super.onCreate(savedInstanceState)
30
31
  Log.d(TAG, "CallActivity onCreate")
31
- window.addFlags(
32
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
33
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
34
- WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
35
- )
32
+
33
+ // Modern way to handle lock screen bypass (API 27+)
34
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
35
+ setShowWhenLocked(true)
36
+ setTurnScreenOn(true)
37
+ } else {
38
+ // Legacy approach for older versions
39
+ window.addFlags(
40
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
41
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
42
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
43
+ )
44
+ }
45
+
36
46
  setContentView(R.layout.activity_call)
37
47
 
38
48
  callId = intent.getStringExtra("callId") ?: ""
39
49
  callType = intent.getStringExtra("callType") ?: "Audio"
40
50
  Log.d(TAG, "CallActivity received callId: $callId, callType: $callType")
41
51
 
42
- CallEngine.cancelIncomingCallUI(this)
52
+ // Cancel incoming call UI immediately when CallActivity shows
53
+ // This is handled in the core answer function now
43
54
 
44
55
  val callerName = intent.getStringExtra("callerName") ?: "Unknown"
45
56
  val nameView = findViewById<TextView>(R.id.caller_name)
@@ -51,7 +62,9 @@ class CallActivity : Activity() {
51
62
  answerBtn.setOnClickListener {
52
63
  Log.d(TAG, "CallActivity: Answer button clicked for callId: $callId")
53
64
  finishReason = FinishReason.ANSWER
54
- CallEngine.bringAppToForeground(this)
65
+
66
+ // Use the single core answer function
67
+ CallEngine.answerCall(this, callId)
55
68
  finishCallActivity()
56
69
  }
57
70
 
@@ -69,7 +82,7 @@ class CallActivity : Activity() {
69
82
  super.onDestroy()
70
83
  Log.d(TAG, "CallActivity onDestroy for callId: $callId. Reason: $finishReason")
71
84
  timeoutHandler.removeCallbacks(timeoutRunnable)
72
- CallEngine.stopRingtone()
85
+ // Don't stop ringtone here - it's handled in the core functions
73
86
  }
74
87
 
75
88
  override fun onBackPressed() {
@@ -28,6 +28,7 @@ import org.json.JSONArray
28
28
  import org.json.JSONObject
29
29
  import java.util.UUID
30
30
  import java.util.concurrent.ConcurrentHashMap
31
+ import android.view.WindowManager
31
32
 
32
33
  object CallEngine {
33
34
  private const val TAG = "CallEngine"
@@ -174,7 +175,6 @@ object CallEngine {
174
175
  notifyCallStateChanged(context)
175
176
  }
176
177
 
177
- // CORRECTED: Uses TelecomManager.placeCall for outgoing calls.
178
178
  fun startOutgoingCall(context: Context, callId: String, callData: String) {
179
179
  appContext = context.applicationContext
180
180
  Log.d(TAG, "startOutgoingCall: $callId, $callData")
@@ -218,7 +218,7 @@ object CallEngine {
218
218
  startForegroundService(context)
219
219
  Log.d(TAG, "Successfully reported outgoing call to TelecomManager via placeCall for $callId")
220
220
  startRingback()
221
- bringAppToForeground(context)
221
+ // Don't bring app to foreground here - wait for answer
222
222
  keepScreenAwake(context, true)
223
223
  if (parsedCallType == "Video") {
224
224
  setAudioRoute(context, "Speaker")
@@ -235,33 +235,54 @@ object CallEngine {
235
235
  notifyCallStateChanged(context)
236
236
  }
237
237
 
238
- fun callAnsweredFromJS(context: Context, callId: String) {
239
- Log.d(TAG, "callAnsweredFromJS: $callId - remote party answered")
238
+ // *** SINGLE SOURCE OF TRUTH: Core Answer Function ***
239
+ private fun coreAnswerCall(context: Context, callId: String, isLocalAnswer: Boolean) {
240
+ Log.d(TAG, "coreAnswerCall: $callId, isLocalAnswer: $isLocalAnswer")
241
+
242
+ // Stop all notification sounds and UI immediately
243
+ stopRingtone()
240
244
  stopRingback()
245
+ cancelIncomingCallUI(context)
246
+
247
+ // Update call state
241
248
  activeCalls[callId]?.state = CallState.ACTIVE
242
249
  currentCallId = callId
250
+
251
+ // Handle multi-call scenario
243
252
  if (!canMakeMultipleCalls) {
244
253
  activeCalls.filter { it.key != callId }.values.forEach { it.state = CallState.HELD }
245
254
  }
246
- notifyCallStateChanged(context)
255
+
256
+ // Emit event
247
257
  emitEvent(CallEventType.CALL_ANSWERED, JSONObject().put("callId", callId))
258
+
259
+ // Start foreground service if not already started
260
+ startForegroundService(context)
261
+
262
+ // Wake up screen and clear lock screen if needed
248
263
  keepScreenAwake(context, true)
264
+
265
+ // Bring app to foreground NOW (not earlier)
266
+ bringAppToForeground(context)
267
+
268
+ // Reset audio mode
249
269
  resetAudioMode(context)
270
+
271
+ // Notify state change
272
+ notifyCallStateChanged(context)
273
+
274
+ Log.d(TAG, "coreAnswerCall completed for $callId")
275
+ }
276
+
277
+ // Public answer methods that delegate to the core function
278
+ fun callAnsweredFromJS(context: Context, callId: String) {
279
+ Log.d(TAG, "callAnsweredFromJS: $callId - remote party answered")
280
+ coreAnswerCall(context, callId, false)
250
281
  }
251
282
 
252
283
  fun answerCall(context: Context, callId: String) {
253
284
  Log.d(TAG, "answerCall: $callId - local party answering")
254
- stopRingtone()
255
- activeCalls[callId]?.state = CallState.ACTIVE
256
- currentCallId = callId
257
- if (!canMakeMultipleCalls) {
258
- activeCalls.filter { it.key != callId }.values.forEach { it.state = CallState.HELD }
259
- }
260
- emitEvent(CallEventType.CALL_ANSWERED, JSONObject().put("callId", callId))
261
- notifyCallStateChanged(context)
262
- startForegroundService(context)
263
- keepScreenAwake(context, true)
264
- resetAudioMode(context)
285
+ coreAnswerCall(context, callId, true)
265
286
  }
266
287
 
267
288
  fun holdCall(context: Context, callId: String) {
@@ -327,6 +348,7 @@ object CallEngine {
327
348
  stopForegroundService(context)
328
349
  keepScreenAwake(context, false)
329
350
  resetAudioMode(context)
351
+ clearLockScreenBypass(context) // Clear lock screen bypass when all calls end
330
352
  } else {
331
353
  Log.d(TAG, "Other calls still active. Foreground service remains.")
332
354
  }
@@ -353,6 +375,7 @@ object CallEngine {
353
375
  stopRingback()
354
376
  keepScreenAwake(context, false)
355
377
  resetAudioMode(context)
378
+ clearLockScreenBypass(context) // Clear lock screen bypass
356
379
  notifyCallStateChanged(context)
357
380
  }
358
381
 
@@ -360,6 +383,34 @@ object CallEngine {
360
383
  fun getCurrentCallId(): String? = currentCallId
361
384
  fun isCallActive(): Boolean = activeCalls.any { it.value.state == CallState.ACTIVE || it.value.state == CallState.INCOMING || it.value.state == CallState.DIALING }
362
385
 
386
+ // --- Clear Lock Screen Bypass ---
387
+ private fun clearLockScreenBypass(context: Context) {
388
+ Log.d(TAG, "Clearing lock screen bypass for all activities.")
389
+
390
+ // Send broadcast to MainActivity to clear flags if it's active
391
+ val intent = Intent("com.pingme2022.CLEAR_LOCK_SCREEN_BYPASS")
392
+ context.sendBroadcast(intent)
393
+
394
+ // Also try to clear on the app context activities
395
+ try {
396
+ if (context is Activity) {
397
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
398
+ (context as Activity).setShowWhenLocked(false)
399
+ (context as Activity).setTurnScreenOn(false)
400
+ } else {
401
+ context.window.clearFlags(
402
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
403
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
404
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
405
+ )
406
+ }
407
+ Log.d(TAG, "Cleared lock screen bypass flags from activity context.")
408
+ }
409
+ } catch (e: Exception) {
410
+ Log.w(TAG, "Could not clear lock screen bypass from activity context: ${e.message}")
411
+ }
412
+ }
413
+
363
414
  // --- Notification/UI/Foreground ---
364
415
 
365
416
  fun showIncomingCallUI(context: Context, callId: String, callerName: String, callType: String) {
@@ -443,14 +494,8 @@ object CallEngine {
443
494
  val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
444
495
  launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
445
496
 
446
- // If there are active calls, add the lock screen bypass flags
447
497
  if (isCallActive()) {
448
- launchIntent?.addFlags(
449
- Intent.FLAG_ACTIVITY_SHOW_WHEN_LOCKED or
450
- Intent.FLAG_ACTIVITY_TURN_SCREEN_ON or
451
- Intent.FLAG_ACTIVITY_DISMISS_KEYGUARD
452
- )
453
- Log.d(TAG, "App brought to foreground with lock screen bypass flags due to active call")
498
+ Log.d(TAG, "App brought to foreground due to active call")
454
499
  } else {
455
500
  Log.d(TAG, "App brought to foreground via normal launchIntent")
456
501
  }
@@ -458,8 +503,10 @@ object CallEngine {
458
503
  context.startActivity(launchIntent)
459
504
  }
460
505
 
461
- // --- Audio Device Management ---
506
+ // Rest of the methods remain the same...
507
+ // [Audio Device Management, Screen Awake Management, etc. - keeping existing implementations]
462
508
 
509
+ // --- Audio Device Management ---
463
510
  fun getAudioDevices(): AudioRoutesInfo {
464
511
  audioManager = appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: run {
465
512
  Log.e(TAG, "getAudioDevices: AudioManager is null or appContext is not set. Returning default.")
@@ -593,7 +640,9 @@ object CallEngine {
593
640
  }
594
641
  }
595
642
 
596
- // --- Audio Device Change Listener ---
643
+ // Continue with rest of existing methods...
644
+ // [Audio Device Change Listener, Call State Change Notification, etc.]
645
+
597
646
  private val audioDeviceCallback = object : AudioDeviceCallback() {
598
647
  override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
599
648
  Log.d(TAG, "Audio devices added. Emitting AUDIO_DEVICES_CHANGED.")
@@ -631,7 +680,6 @@ object CallEngine {
631
680
  emitEvent(CallEventType.AUDIO_DEVICES_CHANGED, jsonPayload)
632
681
  }
633
682
 
634
- // --- Call State Change Notification ---
635
683
  private fun notifyCallStateChanged(context: Context) {
636
684
  val calls = getActiveCalls()
637
685
  val jsonArray = JSONArray()
@@ -677,7 +725,6 @@ object CallEngine {
677
725
  }
678
726
  }
679
727
 
680
- // PhoneAccount registration is used for both INCOMING and OUTGOING calls when interacting with Telecom
681
728
  private fun registerPhoneAccount(context: Context) {
682
729
  val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
683
730
  val phoneAccountHandle = getPhoneAccountHandle(context)
@@ -705,7 +752,6 @@ object CallEngine {
705
752
  )
706
753
  }
707
754
 
708
- // --- Ringtone Management (for incoming calls, pre-Android S) ---
709
755
  fun playRingtone(context: Context) {
710
756
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
711
757
  Log.d(TAG, "playRingtone: Android S+ detected, system will handle ringtone via Telecom.")
@@ -737,7 +783,6 @@ object CallEngine {
737
783
  ringtone = null
738
784
  }
739
785
 
740
- // --- Ringback Tone Management (for outgoing calls) ---
741
786
  private fun startRingback() {
742
787
  if (ringbackPlayer != null && ringbackPlayer!!.isPlaying) {
743
788
  Log.d(TAG, "Ringback tone already playing.")
@@ -15,21 +15,10 @@ class CallNotificationActionReceiver : BroadcastReceiver() {
15
15
 
16
16
  when (intent.action) {
17
17
  "com.qusaieilouti99.callmanager.ANSWER_CALL" -> {
18
- Log.d(TAG, "Answer action received for callId: $callId. Answering call via Telecom.")
18
+ Log.d(TAG, "Answer action received for callId: $callId. Using single core answer function.")
19
19
 
20
- // Get the telecom connection and answer it
21
- val connection = CallEngine.getTelecomConnection(callId)
22
- if (connection != null) {
23
- // This will trigger MyConnection.onAnswer() which handles the rest
24
- connection.onAnswer()
25
- Log.d(TAG, "Call answered via Telecom connection for callId: $callId")
26
- } else {
27
- Log.e(TAG, "No Telecom connection found for callId: $callId. Falling back to direct answer.")
28
- // Fallback: answer directly via CallEngine
29
- CallEngine.answerCall(context, callId)
30
- }
31
-
32
- CallEngine.bringAppToForeground(context)
20
+ // Use the single core answer function instead of multiple pathways
21
+ CallEngine.answerCall(context, callId)
33
22
  }
34
23
  "com.qusaieilouti99.callmanager.DECLINE_CALL" -> {
35
24
  Log.d(TAG, "Decline action received for callId: $callId. Ending call.")
@@ -34,7 +34,7 @@ class MyConnection(
34
34
 
35
35
  // Set connection properties and capabilities
36
36
  connectionProperties = Connection.PROPERTY_SELF_MANAGED
37
- connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or Connection.CAPABILITY_MUTE // Added hold and mute capabilities
37
+ connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or Connection.CAPABILITY_MUTE
38
38
 
39
39
  if (currentCallType == "Video") {
40
40
  Log.d(TAG, "MyConnection for callId $callId initialized as VIDEO call.")
@@ -51,8 +51,9 @@ class MyConnection(
51
51
  override fun onAnswer() {
52
52
  Log.d(TAG, "Call answered via Telecom for callId: $callId")
53
53
  setActive()
54
+
55
+ // Use the single core answer function
54
56
  CallEngine.answerCall(context, callId)
55
- CallEngine.bringAppToForeground(context)
56
57
  }
57
58
 
58
59
  override fun onReject() {
@@ -108,9 +109,6 @@ class MyConnection(
108
109
  override fun onPlayDtmfTone(digit: Char) {
109
110
  super.onPlayDtmfTone(digit)
110
111
  Log.d(TAG, "Playing DTMF tone: $digit for callId: $callId")
111
- // NOTE: DTMF_TONE is not in your current CallEventType enum.
112
- // If you need to emit this event, you MUST add 'DTMF_TONE' to your CallEventType.ts
113
- // and re-run nitro-codegen. For now, it's commented out to prevent compilation error.
114
112
  CallEngine.emitEvent(
115
113
  CallEventType.DTMF_TONE,
116
114
  JSONObject().put("callId", callId).put("digit", digit.toString())
@@ -122,12 +120,10 @@ class MyConnection(
122
120
  Log.d(TAG, "Stopping DTMF tone for callId: $callId")
123
121
  }
124
122
 
125
- // REMOVED: onConnectionEvent override since it doesn't exist in the base Connection class
126
- // If you need connection events, you can send them via sendConnectionEvent() method
127
-
128
123
  override fun onShowIncomingCallUi() {
129
124
  super.onShowIncomingCallUi()
130
- Log.d(TAG, "onShowIncomingCallUi for callId: $callId. Attempting to bring app to foreground.")
131
- CallEngine.bringAppToForeground(context)
125
+ Log.d(TAG, "onShowIncomingCallUi for callId: $callId. Showing CallActivity, not bringing MainActivity to foreground yet.")
126
+ // Don't bring MainActivity to foreground here - only show CallActivity
127
+ // MainActivity will be brought to foreground when the call is answered
132
128
  }
133
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",