@qusaieilouti99/call-manager 0.1.43 → 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.
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallActivity.kt +7 -3
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallEngine.kt +74 -25
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallNotificationActionReceiver.kt +3 -14
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/MyConnection.kt +6 -10
- package/package.json +1 -1
|
@@ -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
|
|
@@ -48,7 +49,8 @@ class CallActivity : Activity() {
|
|
|
48
49
|
callType = intent.getStringExtra("callType") ?: "Audio"
|
|
49
50
|
Log.d(TAG, "CallActivity received callId: $callId, callType: $callType")
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
// Cancel incoming call UI immediately when CallActivity shows
|
|
53
|
+
// This is handled in the core answer function now
|
|
52
54
|
|
|
53
55
|
val callerName = intent.getStringExtra("callerName") ?: "Unknown"
|
|
54
56
|
val nameView = findViewById<TextView>(R.id.caller_name)
|
|
@@ -60,7 +62,9 @@ class CallActivity : Activity() {
|
|
|
60
62
|
answerBtn.setOnClickListener {
|
|
61
63
|
Log.d(TAG, "CallActivity: Answer button clicked for callId: $callId")
|
|
62
64
|
finishReason = FinishReason.ANSWER
|
|
63
|
-
|
|
65
|
+
|
|
66
|
+
// Use the single core answer function
|
|
67
|
+
CallEngine.answerCall(this, callId)
|
|
64
68
|
finishCallActivity()
|
|
65
69
|
}
|
|
66
70
|
|
|
@@ -78,7 +82,7 @@ class CallActivity : Activity() {
|
|
|
78
82
|
super.onDestroy()
|
|
79
83
|
Log.d(TAG, "CallActivity onDestroy for callId: $callId. Reason: $finishReason")
|
|
80
84
|
timeoutHandler.removeCallbacks(timeoutRunnable)
|
|
81
|
-
|
|
85
|
+
// Don't stop ringtone here - it's handled in the core functions
|
|
82
86
|
}
|
|
83
87
|
|
|
84
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,10 +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
|
-
// The deprecated lock screen flags have been removed
|
|
447
|
-
// Lock screen bypass is now handled in the target activity (CallActivity, MainActivity)
|
|
448
497
|
if (isCallActive()) {
|
|
449
|
-
Log.d(TAG, "App brought to foreground due to active call
|
|
498
|
+
Log.d(TAG, "App brought to foreground due to active call")
|
|
450
499
|
} else {
|
|
451
500
|
Log.d(TAG, "App brought to foreground via normal launchIntent")
|
|
452
501
|
}
|
|
@@ -454,8 +503,10 @@ object CallEngine {
|
|
|
454
503
|
context.startActivity(launchIntent)
|
|
455
504
|
}
|
|
456
505
|
|
|
457
|
-
//
|
|
506
|
+
// Rest of the methods remain the same...
|
|
507
|
+
// [Audio Device Management, Screen Awake Management, etc. - keeping existing implementations]
|
|
458
508
|
|
|
509
|
+
// --- Audio Device Management ---
|
|
459
510
|
fun getAudioDevices(): AudioRoutesInfo {
|
|
460
511
|
audioManager = appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: run {
|
|
461
512
|
Log.e(TAG, "getAudioDevices: AudioManager is null or appContext is not set. Returning default.")
|
|
@@ -589,7 +640,9 @@ object CallEngine {
|
|
|
589
640
|
}
|
|
590
641
|
}
|
|
591
642
|
|
|
592
|
-
//
|
|
643
|
+
// Continue with rest of existing methods...
|
|
644
|
+
// [Audio Device Change Listener, Call State Change Notification, etc.]
|
|
645
|
+
|
|
593
646
|
private val audioDeviceCallback = object : AudioDeviceCallback() {
|
|
594
647
|
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
|
|
595
648
|
Log.d(TAG, "Audio devices added. Emitting AUDIO_DEVICES_CHANGED.")
|
|
@@ -627,7 +680,6 @@ object CallEngine {
|
|
|
627
680
|
emitEvent(CallEventType.AUDIO_DEVICES_CHANGED, jsonPayload)
|
|
628
681
|
}
|
|
629
682
|
|
|
630
|
-
// --- Call State Change Notification ---
|
|
631
683
|
private fun notifyCallStateChanged(context: Context) {
|
|
632
684
|
val calls = getActiveCalls()
|
|
633
685
|
val jsonArray = JSONArray()
|
|
@@ -673,7 +725,6 @@ object CallEngine {
|
|
|
673
725
|
}
|
|
674
726
|
}
|
|
675
727
|
|
|
676
|
-
// PhoneAccount registration is used for both INCOMING and OUTGOING calls when interacting with Telecom
|
|
677
728
|
private fun registerPhoneAccount(context: Context) {
|
|
678
729
|
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
|
679
730
|
val phoneAccountHandle = getPhoneAccountHandle(context)
|
|
@@ -701,7 +752,6 @@ object CallEngine {
|
|
|
701
752
|
)
|
|
702
753
|
}
|
|
703
754
|
|
|
704
|
-
// --- Ringtone Management (for incoming calls, pre-Android S) ---
|
|
705
755
|
fun playRingtone(context: Context) {
|
|
706
756
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
707
757
|
Log.d(TAG, "playRingtone: Android S+ detected, system will handle ringtone via Telecom.")
|
|
@@ -733,7 +783,6 @@ object CallEngine {
|
|
|
733
783
|
ringtone = null
|
|
734
784
|
}
|
|
735
785
|
|
|
736
|
-
// --- Ringback Tone Management (for outgoing calls) ---
|
|
737
786
|
private fun startRingback() {
|
|
738
787
|
if (ringbackPlayer != null && ringbackPlayer!!.isPlaying) {
|
|
739
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.
|
|
18
|
+
Log.d(TAG, "Answer action received for callId: $callId. Using single core answer function.")
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
|
|
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
|
|
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.
|
|
131
|
-
|
|
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
|
}
|