rns-nativecall 0.7.9 → 0.8.1
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/rnsnativecall/AcceptCallActivity.kt +13 -62
- package/android/src/main/java/com/rnsnativecall/CallActionReceiver.kt +46 -25
- package/android/src/main/java/com/rnsnativecall/CallForegroundService.kt +54 -31
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +0 -1
- package/android/src/main/java/com/rnsnativecall/CallModule.kt +26 -5
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +89 -98
- package/android/src/main/java/com/rnsnativecall/NotificationOverlayActivity.kt +26 -0
- package/android/src/main/java/com/rnsnativecall/UnlockReceiver.kt +22 -0
- package/android/src/main/res/drawable/circle_background.xml +4 -0
- package/android/src/main/res/drawable/ic_call_answer_white.xml +9 -0
- package/android/src/main/res/drawable/ic_call_end_white.xml +9 -0
- package/android/src/main/res/layout/activity_incoming_call.xml +139 -0
- package/index.d.ts +6 -0
- package/index.js +15 -0
- package/ios/CallModule.m +56 -26
- package/package.json +1 -1
- package/withNativeCallVoip.js +130 -68
- package/android/src/main/java/com/rnsnativecall/UnlockPromptActivity.kt +0 -55
|
@@ -1,77 +1,28 @@
|
|
|
1
1
|
package com.rnsnativecall
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
|
-
import android.app.NotificationManager
|
|
5
|
-
import android.content.Context
|
|
6
4
|
import android.content.Intent
|
|
7
5
|
import android.os.Bundle
|
|
8
6
|
import android.view.WindowManager
|
|
9
|
-
import android.
|
|
7
|
+
import android.os.Build
|
|
10
8
|
|
|
11
9
|
class AcceptCallActivity : Activity() {
|
|
12
|
-
|
|
13
10
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
14
11
|
super.onCreate(savedInstanceState)
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Check: Did the user actually press "Answer" or did the system auto-launch?
|
|
26
|
-
// If the action is null or doesn't match your Answer action,
|
|
27
|
-
// it means the system is just "preparing" the activity.
|
|
28
|
-
if (intent.action?.startsWith("ACTION_ANSWER") != true) {
|
|
29
|
-
// If it's just an auto-launch, we don't fire the JS event!
|
|
30
|
-
// We can either finish() or show a tiny "Swipe to Answer" UI.
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
processCallIntent(intent)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private fun processCallIntent(intent: Intent) {
|
|
37
|
-
NativeCallManager.stopRingtone()
|
|
38
|
-
|
|
39
|
-
val extras = intent.extras
|
|
40
|
-
val uuid = extras?.getString("callUuid")
|
|
41
|
-
|
|
42
|
-
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
43
|
-
uuid?.let { notificationManager.cancel(it.hashCode()) }
|
|
44
|
-
|
|
45
|
-
// ✅ WE STOP SENDING THE JS EVENT HERE.
|
|
46
|
-
// Instead, we pass the intent to MainActivity with a specific ACTION.
|
|
47
|
-
openMainApp(extras)
|
|
48
|
-
finish()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private fun openMainApp(extras: Bundle?) {
|
|
52
|
-
try {
|
|
53
|
-
// Get the actual MainActivity class name (e.g., com.yourapp.MainActivity)
|
|
54
|
-
val mainActivityClassName = "${packageName}.MainActivity"
|
|
55
|
-
|
|
56
|
-
val intent = Intent().apply {
|
|
57
|
-
setClassName(packageName, mainActivityClassName)
|
|
58
|
-
action = "com.rnsnativecall.ACTION_ANSWER"
|
|
59
|
-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
60
|
-
|
|
61
|
-
// Ensure extras are carried over
|
|
62
|
-
extras?.let { putExtras(it) }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
startActivity(intent)
|
|
66
|
-
} catch (e: Exception) {
|
|
67
|
-
// Fallback: If explicit mapping fails, try the launch intent but force the action
|
|
68
|
-
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
69
|
-
launchIntent?.apply {
|
|
70
|
-
action = "com.rnsnativecall.ACTION_ANSWER"
|
|
71
|
-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
72
|
-
extras?.let { putExtras(it) }
|
|
73
|
-
startActivity(this)
|
|
13
|
+
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
14
|
+
if (launchIntent != null) {
|
|
15
|
+
launchIntent.apply {
|
|
16
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
17
|
+
Intent.FLAG_ACTIVITY_SINGLE_TOP or
|
|
18
|
+
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
|
19
|
+
putExtras(intent.extras ?: Bundle())
|
|
20
|
+
putExtra("navigatingToCall", true)
|
|
74
21
|
}
|
|
22
|
+
startActivity(launchIntent)
|
|
75
23
|
}
|
|
24
|
+
|
|
25
|
+
finish()
|
|
26
|
+
overridePendingTransition(0, 0)
|
|
76
27
|
}
|
|
77
28
|
}
|
|
@@ -4,49 +4,70 @@ import android.content.BroadcastReceiver
|
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.content.Intent
|
|
6
6
|
import android.app.NotificationManager
|
|
7
|
-
import android.
|
|
7
|
+
import android.os.Bundle
|
|
8
8
|
|
|
9
9
|
class CallActionReceiver : BroadcastReceiver() {
|
|
10
10
|
override fun onReceive(context: Context, intent: Intent) {
|
|
11
|
+
// Stop sound immediately
|
|
11
12
|
NativeCallManager.stopRingtone()
|
|
12
13
|
|
|
13
14
|
val uuid = intent.getStringExtra("EXTRA_CALL_UUID") ?: return
|
|
14
15
|
val action = intent.action ?: ""
|
|
16
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
val dataMap = mutableMapOf<String, String>()
|
|
18
|
+
// 1. Reconstruct the full data map from Intent Extras
|
|
19
|
+
val fullDataMap = mutableMapOf<String, String>()
|
|
20
20
|
intent.extras?.keySet()?.forEach { key ->
|
|
21
|
-
intent.extras?.get(key)?.let {
|
|
21
|
+
intent.extras?.get(key)?.let { fullDataMap[key] = it.toString() }
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
val name =
|
|
25
|
-
val callType =
|
|
26
|
-
|
|
27
|
-
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
24
|
+
val name = fullDataMap["name"] ?: "Someone"
|
|
25
|
+
val callType = fullDataMap["callType"] ?: "audio"
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
// --- HANDLE REJECT ---
|
|
28
|
+
if (action == "ACTION_REJECT") {
|
|
30
29
|
if (CallModule.isReady()) {
|
|
31
|
-
CallModule.sendEventToJS("onCallRejected",
|
|
30
|
+
CallModule.sendEventToJS("onCallRejected", fullDataMap)
|
|
32
31
|
notificationManager.cancel(uuid.hashCode())
|
|
32
|
+
CallForegroundService.stop(context)
|
|
33
33
|
} else {
|
|
34
|
-
|
|
35
|
-
CallModule.setPendingCallData("onCallRejected_pending", mapOf("callUuid" to uuid))
|
|
36
|
-
// Update notification pill to "Aborting…" state
|
|
34
|
+
CallModule.setPendingCallData("onCallRejected_pending", fullDataMap)
|
|
37
35
|
NativeCallManager.aborting(context, uuid, name, callType)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- HANDLE ANSWER ---
|
|
40
|
+
if (action == "ACTION_ANSWER") {
|
|
41
|
+
// Dismiss the "Incoming" pill immediately
|
|
42
|
+
notificationManager.cancel(uuid.hashCode())
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
if (CallModule.isReady()) {
|
|
45
|
+
// Inform JS with full payload
|
|
46
|
+
CallModule.sendEventToJS("onCallAccepted", fullDataMap)
|
|
47
|
+
CallForegroundService.stop(context)
|
|
48
|
+
|
|
49
|
+
// Open the app
|
|
50
|
+
launchApp(context, intent.extras)
|
|
51
|
+
} else {
|
|
52
|
+
// Cold start: Queue full payload and show connecting
|
|
53
|
+
CallModule.setPendingCallData("onCallAccepted_pending", fullDataMap)
|
|
54
|
+
NativeCallManager.connecting(context, uuid, name, callType)
|
|
55
|
+
|
|
56
|
+
launchApp(context, intent.extras)
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
private fun launchApp(context: Context, extras: Bundle?) {
|
|
63
|
+
val intent = Intent(context, AcceptCallActivity::class.java).apply {
|
|
64
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
65
|
+
Intent.FLAG_ACTIVITY_SINGLE_TOP or
|
|
66
|
+
Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
67
|
+
|
|
68
|
+
if (extras != null) putExtras(extras)
|
|
69
|
+
}
|
|
70
|
+
context.startActivity(intent)
|
|
52
71
|
}
|
|
72
|
+
|
|
73
|
+
}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
package com.rnsnativecall
|
|
2
2
|
|
|
3
3
|
import android.app.*
|
|
4
|
+
import android.content.BroadcastReceiver
|
|
4
5
|
import android.content.Context
|
|
5
6
|
import android.content.Intent
|
|
7
|
+
import android.content.IntentFilter
|
|
6
8
|
import android.content.pm.ServiceInfo
|
|
7
9
|
import android.os.Build
|
|
8
10
|
import android.os.Bundle
|
|
9
11
|
import android.os.IBinder
|
|
10
12
|
import androidx.core.app.NotificationCompat
|
|
11
13
|
import com.facebook.react.HeadlessJsTaskService
|
|
12
|
-
|
|
13
|
-
import android.os.
|
|
14
|
-
import android.os.Looper // Added
|
|
14
|
+
import android.os.Handler
|
|
15
|
+
import android.os.Looper
|
|
15
16
|
|
|
16
17
|
class CallForegroundService : Service() {
|
|
17
18
|
|
|
19
|
+
private var unlockReceiver: UnlockReceiver? = null // Store reference for unregistering
|
|
20
|
+
|
|
18
21
|
companion object {
|
|
19
22
|
private const val NOTIFICATION_ID = 101
|
|
20
23
|
private const val CHANNEL_ID = "incoming_call_service"
|
|
@@ -25,6 +28,23 @@ class CallForegroundService : Service() {
|
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
override fun onCreate() {
|
|
32
|
+
super.onCreate()
|
|
33
|
+
|
|
34
|
+
// --- DYNAMIC REGISTRATION FIX ---
|
|
35
|
+
// Registering here makes the system allow the USER_PRESENT broadcast
|
|
36
|
+
// because the app is already in the Foreground (via this service).
|
|
37
|
+
unlockReceiver = UnlockReceiver()
|
|
38
|
+
val filter = IntentFilter(Intent.ACTION_USER_PRESENT)
|
|
39
|
+
|
|
40
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
41
|
+
// Android 13+ requires the RECEIVER_EXPORTED flag for system broadcasts
|
|
42
|
+
registerReceiver(unlockReceiver, filter, Context.RECEIVER_EXPORTED)
|
|
43
|
+
} else {
|
|
44
|
+
registerReceiver(unlockReceiver, filter)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
29
49
|
val data = intent?.extras
|
|
30
50
|
val name = data?.getString("name") ?: "Someone"
|
|
@@ -33,14 +53,13 @@ class CallForegroundService : Service() {
|
|
|
33
53
|
|
|
34
54
|
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
|
35
55
|
.setContentTitle(name)
|
|
36
|
-
.setContentText("
|
|
56
|
+
.setContentText("Incoming Call...")
|
|
37
57
|
.setSmallIcon(applicationInfo.icon)
|
|
38
58
|
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
39
59
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
40
60
|
.setOngoing(true)
|
|
41
61
|
.build()
|
|
42
62
|
|
|
43
|
-
// --- ANDROID 14 FIX START ---
|
|
44
63
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
45
64
|
startForeground(
|
|
46
65
|
NOTIFICATION_ID,
|
|
@@ -50,11 +69,9 @@ class CallForegroundService : Service() {
|
|
|
50
69
|
} else {
|
|
51
70
|
startForeground(NOTIFICATION_ID, notification)
|
|
52
71
|
}
|
|
53
|
-
// --- ANDROID 14 FIX END ---
|
|
54
72
|
|
|
55
73
|
// Launch the Headless Task
|
|
56
74
|
val headlessIntent = Intent(this, CallHeadlessTask::class.java).apply {
|
|
57
|
-
// Safe bundle copy
|
|
58
75
|
val bundle = Bundle()
|
|
59
76
|
data?.let { b ->
|
|
60
77
|
for (key in b.keySet()) {
|
|
@@ -67,38 +84,44 @@ class CallForegroundService : Service() {
|
|
|
67
84
|
try {
|
|
68
85
|
this.startService(headlessIntent)
|
|
69
86
|
HeadlessJsTaskService.acquireWakeLockNow(this)
|
|
70
|
-
} catch (e: Exception) {
|
|
71
|
-
e.printStackTrace()
|
|
72
|
-
}
|
|
87
|
+
} catch (e: Exception) { e.printStackTrace() }
|
|
73
88
|
|
|
89
|
+
// Auto-stop after 30s
|
|
90
|
+
Handler(Looper.getMainLooper()).postDelayed({
|
|
91
|
+
try { stopSelf() } catch (e: Exception) {}
|
|
92
|
+
}, 30000)
|
|
74
93
|
|
|
75
|
-
// Automatically stop this service if JS doesn't stop it within 30 seconds
|
|
76
|
-
Handler(Looper.getMainLooper()).postDelayed({
|
|
77
|
-
try {
|
|
78
|
-
stopSelf()
|
|
79
|
-
} catch (e: Exception) {
|
|
80
|
-
// Service might already be stopped
|
|
81
|
-
}
|
|
82
|
-
}, 30000)
|
|
83
94
|
return START_NOT_STICKY
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
override fun onDestroy() {
|
|
98
|
+
// --- CLEANUP ---
|
|
99
|
+
// Unregister to prevent memory leaks once the call ends or service stops
|
|
100
|
+
try {
|
|
101
|
+
unlockReceiver?.let { unregisterReceiver(it) }
|
|
102
|
+
} catch (e: Exception) { e.printStackTrace() }
|
|
103
|
+
|
|
104
|
+
try { stopForeground(true) } catch (_: Exception) {}
|
|
105
|
+
super.onDestroy()
|
|
106
|
+
}
|
|
107
|
+
|
|
86
108
|
private fun createNotificationChannel() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
110
|
+
val channel = NotificationChannel(
|
|
111
|
+
CHANNEL_ID,
|
|
112
|
+
"Call Service",
|
|
113
|
+
NotificationManager.IMPORTANCE_LOW
|
|
114
|
+
).apply {
|
|
115
|
+
description = "Handles incoming call connection state"
|
|
116
|
+
setSound(null, null)
|
|
117
|
+
enableVibration(false)
|
|
118
|
+
setBypassDnd(true)
|
|
119
|
+
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
|
120
|
+
}
|
|
121
|
+
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
122
|
+
manager.createNotificationChannel(channel)
|
|
97
123
|
}
|
|
98
|
-
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
99
|
-
manager.createNotificationChannel(channel)
|
|
100
124
|
}
|
|
101
|
-
}
|
|
102
125
|
|
|
103
126
|
override fun onBind(intent: Intent?): IBinder? = null
|
|
104
127
|
}
|
|
@@ -34,7 +34,6 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
34
34
|
CallState.clear(uuid, context)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// Dismiss the "Connecting..." or "Incoming Call" UI
|
|
38
37
|
NativeCallManager.dismissIncomingCall(context, uuid)
|
|
39
38
|
showMissedCallNotification(context, data, uuid)
|
|
40
39
|
return
|
|
@@ -5,6 +5,9 @@ import android.content.Context
|
|
|
5
5
|
import com.facebook.react.bridge.*
|
|
6
6
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
7
7
|
import android.content.Intent
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.provider.Settings
|
|
10
|
+
import android.net.Uri
|
|
8
11
|
|
|
9
12
|
class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
10
13
|
|
|
@@ -51,6 +54,29 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
// Inside your CallModule class
|
|
58
|
+
@ReactMethod
|
|
59
|
+
fun checkOverlayPermission(promise: Promise) {
|
|
60
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
61
|
+
promise.resolve(Settings.canDrawOverlays(reactApplicationContext))
|
|
62
|
+
} else {
|
|
63
|
+
promise.resolve(true)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@ReactMethod
|
|
68
|
+
fun requestOverlayPermission() {
|
|
69
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
70
|
+
if (!Settings.canDrawOverlays(reactApplicationContext)) {
|
|
71
|
+
val intent = Intent(
|
|
72
|
+
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
|
73
|
+
Uri.parse("package:${reactApplicationContext.packageName}")
|
|
74
|
+
)
|
|
75
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
76
|
+
reactApplicationContext.startActivity(intent)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
54
80
|
|
|
55
81
|
/**
|
|
56
82
|
* Combined Validity Check:
|
|
@@ -108,11 +134,6 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
108
134
|
notificationManager.cancel(101)
|
|
109
135
|
}
|
|
110
136
|
|
|
111
|
-
@ReactMethod
|
|
112
|
-
fun checkTelecomPermissions(promise: Promise) {
|
|
113
|
-
promise.resolve(true)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
137
|
@ReactMethod fun addListener(eventName: String) {}
|
|
117
138
|
@ReactMethod fun removeListeners(count: Int) {}
|
|
118
139
|
|
|
@@ -10,85 +10,89 @@ import androidx.core.app.NotificationCompat
|
|
|
10
10
|
import android.media.Ringtone
|
|
11
11
|
import android.media.RingtoneManager
|
|
12
12
|
import android.graphics.Color
|
|
13
|
-
|
|
14
13
|
import android.app.KeyguardManager
|
|
15
14
|
|
|
16
|
-
import com.rnsnativecall.CallMessagingService
|
|
17
|
-
|
|
18
15
|
object NativeCallManager {
|
|
19
16
|
|
|
20
17
|
private var ringtone: Ringtone? = null
|
|
21
18
|
const val channelId = "CALL_CHANNEL_ID"
|
|
22
19
|
|
|
20
|
+
private var currentCallData: Map<String, String>? = null
|
|
21
|
+
|
|
22
|
+
fun getCurrentCallData(): Map<String, String>? = currentCallData
|
|
23
|
+
|
|
23
24
|
fun handleIncomingPush(context: Context, data: Map<String, String>) {
|
|
24
25
|
val uuid = data["callUuid"] ?: return
|
|
26
|
+
this.currentCallData = data
|
|
25
27
|
stopRingtone()
|
|
26
28
|
|
|
27
29
|
val name = data["name"] ?: "Incoming Call"
|
|
28
30
|
val callType = data["callType"] ?: "audio"
|
|
29
31
|
val notificationId = uuid.hashCode()
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// --- LOCK SCREEN GATEKEEPER ---
|
|
35
|
-
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
36
|
-
if (keyguardManager.isKeyguardLocked) {
|
|
37
|
-
// Device is locked? We bounce it!
|
|
38
|
-
CallState.markCanceled(uuid, context)
|
|
39
|
-
showMissedCallNotification(context, data, uuid)
|
|
40
|
-
return // 🛑 Flow stops here
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
33
|
val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
46
34
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
|
47
35
|
} else {
|
|
48
36
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
49
37
|
}
|
|
50
38
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
// Dummy intent for content click
|
|
40
|
+
val noOpIntent = PendingIntent.getActivity(context, notificationId + 3, Intent(), pendingFlags)
|
|
41
|
+
|
|
42
|
+
// 1. Define the flags properly (DO NOT MIX THEM)
|
|
43
|
+
val mutableFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
44
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
|
45
|
+
} else {
|
|
46
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
val immutableFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
50
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
51
|
+
} else {
|
|
52
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Setup the Overlay Intent
|
|
56
|
+
val overlayIntent = Intent(context, NotificationOverlayActivity::class.java).apply {
|
|
57
|
+
this.putExtra("EXTRA_CALL_UUID", uuid)
|
|
58
|
+
data.forEach { (k, v) -> this.putExtra(k, v) }
|
|
59
|
+
// No User Action flag prevents the intent from triggering an auto-dismiss of notifications
|
|
60
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. Create the Full Screen PendingIntent (Use IMMUTABLE)
|
|
64
|
+
val fullScreenPendingIntent = PendingIntent.getActivity(
|
|
65
|
+
context,
|
|
66
|
+
notificationId,
|
|
67
|
+
overlayIntent,
|
|
68
|
+
immutableFlags // <--- Use the immutable version here
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
// --- ANSWER ACTION ---
|
|
74
|
+
val answerIntent = Intent(context, CallActionReceiver::class.java).apply {
|
|
75
|
+
this.action = "ACTION_ANSWER"
|
|
76
|
+
this.putExtra("EXTRA_CALL_UUID", uuid)
|
|
77
|
+
|
|
78
|
+
data.forEach { (key, value) -> this.putExtra(key, value) } // Pass full data
|
|
62
79
|
}
|
|
63
80
|
|
|
64
|
-
val
|
|
65
|
-
context,
|
|
66
|
-
notificationId,
|
|
67
|
-
intentToActivity,
|
|
68
|
-
pendingFlags
|
|
69
|
-
)
|
|
81
|
+
val answerPendingIntent = PendingIntent.getBroadcast(context, notificationId + 1, answerIntent, mutableFlags)
|
|
70
82
|
|
|
83
|
+
// --- REJECT ACTION ---
|
|
71
84
|
val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
|
|
72
|
-
this.action = "
|
|
85
|
+
this.action = "ACTION_REJECT"
|
|
73
86
|
this.putExtra("EXTRA_CALL_UUID", uuid)
|
|
74
|
-
data.forEach { (key, value) -> this.putExtra(key, value) }
|
|
87
|
+
data.forEach { (key, value) -> this.putExtra(key, value) } // Pass full data
|
|
75
88
|
}
|
|
76
|
-
|
|
77
|
-
val rejectPendingIntent = PendingIntent.getBroadcast(
|
|
78
|
-
context,
|
|
79
|
-
notificationId,
|
|
80
|
-
rejectIntent,
|
|
81
|
-
pendingFlags
|
|
82
|
-
)
|
|
89
|
+
|
|
90
|
+
val rejectPendingIntent = PendingIntent.getBroadcast(context, notificationId + 2, rejectIntent, mutableFlags)
|
|
83
91
|
|
|
84
92
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
85
93
|
|
|
86
94
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
87
|
-
val channel = NotificationChannel(
|
|
88
|
-
channelId,
|
|
89
|
-
"Incoming Calls",
|
|
90
|
-
NotificationManager.IMPORTANCE_HIGH // NotificationManager.IMPORTANCE_HIGH
|
|
91
|
-
).apply {
|
|
95
|
+
val channel = NotificationChannel(channelId, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
|
|
92
96
|
enableVibration(true)
|
|
93
97
|
vibrationPattern = longArrayOf(0, 500, 500, 500)
|
|
94
98
|
lightColor = Color.GREEN
|
|
@@ -99,83 +103,70 @@ object NativeCallManager {
|
|
|
99
103
|
notificationManager.createNotificationChannel(channel)
|
|
100
104
|
}
|
|
101
105
|
|
|
106
|
+
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
107
|
+
val isLocked = keyguardManager.isKeyguardLocked
|
|
108
|
+
|
|
109
|
+
|
|
102
110
|
val builder = NotificationCompat.Builder(context, channelId)
|
|
103
111
|
.setSmallIcon(context.applicationInfo.icon)
|
|
104
112
|
.setContentTitle("Incoming $callType call")
|
|
105
113
|
.setContentText(name)
|
|
106
|
-
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
107
|
-
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
114
|
+
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
115
|
+
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
108
116
|
.setOngoing(true)
|
|
109
117
|
.setAutoCancel(false)
|
|
110
118
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
111
|
-
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
112
119
|
.setContentIntent(noOpIntent)
|
|
113
|
-
.addAction(0, "Answer",
|
|
120
|
+
.addAction(0, "Answer", answerPendingIntent)
|
|
114
121
|
.addAction(0, "Decline", rejectPendingIntent)
|
|
122
|
+
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
123
|
+
|
|
124
|
+
builder.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
125
|
+
|
|
115
126
|
|
|
116
127
|
notificationManager.notify(notificationId, builder.build())
|
|
117
128
|
|
|
118
129
|
try {
|
|
119
130
|
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
120
131
|
ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
|
|
121
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
|
122
|
-
ringtone?.isLooping = true
|
|
123
|
-
}
|
|
132
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ringtone?.isLooping = true
|
|
124
133
|
ringtone?.play()
|
|
125
|
-
} catch (e: Exception) {
|
|
126
|
-
e.printStackTrace()
|
|
127
|
-
}
|
|
134
|
+
} catch (e: Exception) { e.printStackTrace() }
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
fun stopRingtone() {
|
|
131
138
|
try {
|
|
132
139
|
ringtone?.let { if (it.isPlaying) it.stop() }
|
|
133
140
|
ringtone = null
|
|
134
|
-
} catch (e: Exception) {
|
|
135
|
-
ringtone = null
|
|
136
|
-
}
|
|
141
|
+
} catch (e: Exception) { ringtone = null }
|
|
137
142
|
}
|
|
138
143
|
|
|
139
|
-
fun connecting(context: Context, uuid: String, name: String, callType: String) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
|
|
150
|
-
.setOngoing(true)
|
|
151
|
-
.setAutoCancel(false)
|
|
152
|
-
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
153
|
-
.setProgress(0, 0, true) // ✅ system activity indicator (indeterminate progress bar)
|
|
154
|
-
|
|
155
|
-
notificationManager.notify(notificationId, builder.build())
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
fun aborting(context: Context, uuid: String, name: String, callType: String) {
|
|
159
|
-
val notificationId = uuid.hashCode()
|
|
160
|
-
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
161
|
-
|
|
162
|
-
val builder = NotificationCompat.Builder(context, channelId)
|
|
163
|
-
.setSmallIcon(context.applicationInfo.icon)
|
|
164
|
-
.setContentTitle("Incoming $callType call")
|
|
165
|
-
.setContentText("Aborting…") // ✅ show aborting text
|
|
166
|
-
.setSubText("Aborting…") // status line
|
|
167
|
-
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
168
|
-
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
169
|
-
.setOngoing(true)
|
|
170
|
-
.setAutoCancel(false)
|
|
171
|
-
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
172
|
-
.setProgress(0, 0, true) // ✅ indeterminate progress indicator
|
|
173
|
-
|
|
174
|
-
notificationManager.notify(notificationId, builder.build())
|
|
175
|
-
}
|
|
144
|
+
fun connecting(context: Context, uuid: String, name: String, callType: String) {
|
|
145
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
146
|
+
val builder = NotificationCompat.Builder(context, channelId)
|
|
147
|
+
.setSmallIcon(context.applicationInfo.icon)
|
|
148
|
+
.setContentTitle("Incoming $callType call")
|
|
149
|
+
.setContentText("Connecting…")
|
|
150
|
+
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
151
|
+
.setOngoing(true)
|
|
152
|
+
.setProgress(0, 0, true)
|
|
153
|
+
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
154
|
+
}
|
|
176
155
|
|
|
156
|
+
fun aborting(context: Context, uuid: String, name: String, callType: String) {
|
|
157
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
158
|
+
val builder = NotificationCompat.Builder(context, channelId)
|
|
159
|
+
.setSmallIcon(context.applicationInfo.icon)
|
|
160
|
+
.setContentTitle("Incoming $callType call")
|
|
161
|
+
.setContentText("Aborting…")
|
|
162
|
+
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
163
|
+
.setOngoing(true)
|
|
164
|
+
.setProgress(0, 0, true)
|
|
165
|
+
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
166
|
+
}
|
|
177
167
|
|
|
178
168
|
fun dismissIncomingCall(context: Context, uuid: String?) {
|
|
169
|
+
this.currentCallData = null
|
|
179
170
|
stopRingtone()
|
|
180
171
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
181
172
|
if (uuid != null) notificationManager.cancel(uuid.hashCode())
|