rns-nativecall 0.4.6 → 0.4.8
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.
|
@@ -15,8 +15,6 @@ import com.google.firebase.messaging.FirebaseMessagingService
|
|
|
15
15
|
import com.google.firebase.messaging.RemoteMessage
|
|
16
16
|
import com.facebook.react.HeadlessJsTaskService
|
|
17
17
|
|
|
18
|
-
import android.os.PowerManager
|
|
19
|
-
|
|
20
18
|
class CallMessagingService : FirebaseMessagingService() {
|
|
21
19
|
|
|
22
20
|
companion object {
|
|
@@ -41,36 +39,33 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
if (isAppInForeground(context)) {
|
|
42
|
+
// When in foreground, we just tell JS to show the in-app UI
|
|
44
43
|
CallModule.sendEventToJS("onCallReceived", data)
|
|
45
44
|
} else {
|
|
46
|
-
// 1.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
data.forEach { (k, v) -> putString(k, v) }
|
|
50
|
-
})
|
|
51
|
-
}
|
|
45
|
+
// 1. Show the Notification IMMEDIATELY
|
|
46
|
+
// Do not wait for HeadlessJS. Android requires immediate feedback for background starts.
|
|
47
|
+
NativeCallManager.handleIncomingPush(context, data)
|
|
52
48
|
|
|
53
|
-
// 2. Start
|
|
54
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
55
|
-
context.startForegroundService(headlessIntent)
|
|
56
|
-
} else {
|
|
57
|
-
context.startService(headlessIntent)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 3. WakeLock
|
|
49
|
+
// 2. Start HeadlessJS Task to sync Javascript state (WebRTC initialization, etc.)
|
|
61
50
|
try {
|
|
51
|
+
val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
|
|
52
|
+
putExtras(Bundle().apply {
|
|
53
|
+
data.forEach { (k, v) -> putString(k, v) }
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Use a standard startService. If CallHeadlessTask is a HeadlessJsTaskService,
|
|
58
|
+
// it handles its own foreground/background lifecycle.
|
|
59
|
+
context.startService(headlessIntent)
|
|
62
60
|
HeadlessJsTaskService.acquireWakeLockNow(context)
|
|
63
|
-
} catch (e: Exception) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
val wakeLock = powerManager.newWakeLock(
|
|
67
|
-
PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE,
|
|
68
|
-
"NativeCall:IncomingCallWakeLock"
|
|
69
|
-
)
|
|
70
|
-
wakeLock.acquire(3000) // Wake screen for 3 seconds to show notification
|
|
61
|
+
} catch (e: Exception) {
|
|
62
|
+
e.printStackTrace()
|
|
63
|
+
}
|
|
71
64
|
|
|
72
|
-
//
|
|
65
|
+
// 3. Backup Safety: Ensure if JS fails, the notification is eventually handled
|
|
73
66
|
val showNotificationRunnable = Runnable {
|
|
67
|
+
// If the notification was somehow canceled or not showing, re-trigger
|
|
68
|
+
// (Usually not needed if handleIncomingPush is called above, but kept for your logic)
|
|
74
69
|
if (!isAppInForeground(context)) {
|
|
75
70
|
NativeCallManager.handleIncomingPush(context, data)
|
|
76
71
|
}
|
|
@@ -1,130 +1,130 @@
|
|
|
1
1
|
package com.rnsnativecall
|
|
2
2
|
|
|
3
|
-
import android.app.Notification
|
|
4
3
|
import android.app.NotificationChannel
|
|
5
4
|
import android.app.NotificationManager
|
|
6
5
|
import android.app.PendingIntent
|
|
7
6
|
import android.content.Context
|
|
8
|
-
import android.content.Intent
|
|
9
|
-
import android.graphics.Color
|
|
10
|
-
import android.media.AudioAttributes
|
|
11
|
-
import android.media.RingtoneManager
|
|
7
|
+
import android.content.Intent // CRITICAL IMPORT
|
|
12
8
|
import android.os.Build
|
|
9
|
+
import android.os.Bundle // CRITICAL IMPORT
|
|
13
10
|
import androidx.core.app.NotificationCompat
|
|
11
|
+
import android.media.Ringtone
|
|
12
|
+
import android.media.RingtoneManager
|
|
13
|
+
import android.graphics.Color
|
|
14
14
|
import androidx.core.app.Person
|
|
15
|
-
import
|
|
15
|
+
import androidx.core.app.NotificationCompat.CallStyle
|
|
16
16
|
|
|
17
17
|
object NativeCallManager {
|
|
18
18
|
|
|
19
|
+
private var ringtone: Ringtone? = null
|
|
19
20
|
const val CALL_CHANNEL_ID = "CALL_CHANNEL_ID"
|
|
20
21
|
|
|
21
22
|
fun handleIncomingPush(context: Context, data: Map<String, String>) {
|
|
22
23
|
val uuid = data["callUuid"] ?: return
|
|
23
|
-
val name = data["name"] ?: "Unknown Caller"
|
|
24
|
-
val handle = data["handle"] ?: ""
|
|
25
24
|
stopRingtone()
|
|
26
|
-
|
|
25
|
+
|
|
26
|
+
val name = data["name"] ?: "Someone"
|
|
27
|
+
val callType = data["callType"] ?: "audio"
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
|
33
|
-
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
34
|
-
.build()
|
|
35
|
-
|
|
36
|
-
val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
|
|
37
|
-
setSound(ringtoneUri, audioAttributes)
|
|
38
|
-
enableVibration(true)
|
|
39
|
-
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
40
|
-
setBypassDnd(true) // Optional: Break through Do Not Disturb
|
|
41
|
-
}
|
|
42
|
-
notificationManager.createNotificationChannel(channel)
|
|
29
|
+
val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
30
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
|
31
|
+
} else {
|
|
32
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
//
|
|
46
|
-
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
|
47
|
-
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
48
|
-
else PendingIntent.FLAG_UPDATE_CURRENT
|
|
49
|
-
|
|
50
|
-
// A. Full Screen Intent (Triggers the Heads-up logic)
|
|
51
|
-
// We point this to AcceptCallActivity, which has showWhenLocked="false"
|
|
52
|
-
// Result: System tries to launch, hits the lock restriction, and falls back to showing the Notification Pill.
|
|
35
|
+
// 1. Full Screen / UI Intent
|
|
53
36
|
val intentToActivity = Intent(context, AcceptCallActivity::class.java).apply {
|
|
54
|
-
action = "
|
|
55
|
-
data.forEach { (
|
|
37
|
+
action = "ACTION_SHOW_UI_$uuid"
|
|
38
|
+
data.forEach { (key, value) -> putExtra(key, value) }
|
|
39
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
56
40
|
}
|
|
57
|
-
val fullScreenPendingIntent = PendingIntent.getActivity(
|
|
41
|
+
val fullScreenPendingIntent = PendingIntent.getActivity(
|
|
42
|
+
context, uuid.hashCode(), intentToActivity, pendingFlags
|
|
43
|
+
)
|
|
58
44
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
launchIntent.apply {
|
|
65
|
-
action = "ACTION_ANSWER_CALL"
|
|
66
|
-
putExtra("callUuid", uuid)
|
|
67
|
-
data.forEach { (k, v) -> putExtra(k, v) }
|
|
68
|
-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
45
|
+
// 2. Reject Intent
|
|
46
|
+
val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
|
|
47
|
+
action = "ACTION_REJECT_$uuid"
|
|
48
|
+
putExtra("EXTRA_CALL_UUID", uuid)
|
|
49
|
+
data.forEach { (key, value) -> putExtra(key, value) }
|
|
69
50
|
}
|
|
70
|
-
val
|
|
51
|
+
val rejectPendingIntent = PendingIntent.getBroadcast(
|
|
52
|
+
context, uuid.hashCode() + 1, rejectIntent, pendingFlags
|
|
53
|
+
)
|
|
71
54
|
|
|
72
|
-
//
|
|
73
|
-
val
|
|
74
|
-
action = "
|
|
75
|
-
putExtra("
|
|
55
|
+
// 3. Answer Intent (Using your CallActionReceiver)
|
|
56
|
+
val answerIntent = Intent(context, CallActionReceiver::class.java).apply {
|
|
57
|
+
action = "ACTION_ACCEPT_$uuid" // Changed to match your Receiver logic
|
|
58
|
+
putExtra("EXTRA_CALL_UUID", uuid)
|
|
59
|
+
data.forEach { (key, value) -> putExtra(key, value) }
|
|
76
60
|
}
|
|
77
|
-
val
|
|
61
|
+
val answerPendingIntent = PendingIntent.getBroadcast(
|
|
62
|
+
context, uuid.hashCode() + 2, answerIntent, pendingFlags
|
|
63
|
+
)
|
|
78
64
|
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
66
|
+
|
|
67
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
68
|
+
val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Call", NotificationManager.IMPORTANCE_HIGH).apply {
|
|
69
|
+
setBypassDnd(true)
|
|
70
|
+
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
|
71
|
+
enableVibration(true)
|
|
72
|
+
setSound(null, null)
|
|
73
|
+
}
|
|
74
|
+
notificationManager.createNotificationChannel(channel)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
val caller = Person.Builder()
|
|
81
78
|
.setName(name)
|
|
82
|
-
.
|
|
79
|
+
.setImportant(true)
|
|
83
80
|
.build()
|
|
84
81
|
|
|
85
82
|
val builder = NotificationCompat.Builder(context, CALL_CHANNEL_ID)
|
|
86
83
|
.setSmallIcon(context.applicationInfo.icon)
|
|
84
|
+
.setContentTitle("Incoming $callType call")
|
|
85
|
+
.setContentText(name)
|
|
87
86
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
88
87
|
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
88
|
+
.setOngoing(true)
|
|
89
89
|
.setAutoCancel(false)
|
|
90
|
-
.
|
|
91
|
-
.setColor(Color.parseColor("#
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
91
|
+
.setColor(Color.parseColor("#28a745"))
|
|
92
|
+
.setColorized(true)
|
|
93
|
+
.setStyle(
|
|
94
|
+
NotificationCompat.CallStyle.forIncomingCall(
|
|
95
|
+
caller,
|
|
96
|
+
rejectPendingIntent,
|
|
97
|
+
answerPendingIntent
|
|
98
|
+
)
|
|
99
99
|
)
|
|
100
|
-
|
|
101
|
-
// Fallback for Android 10/11
|
|
102
|
-
builder.setContentTitle("Incoming Call")
|
|
103
|
-
.setContentText(name)
|
|
104
|
-
.addAction(android.R.drawable.ic_menu_call, "Answer", answerPendingIntent)
|
|
105
|
-
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Decline", rejectPendingIntent)
|
|
106
|
-
}
|
|
107
|
-
|
|
100
|
+
|
|
108
101
|
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
105
|
+
ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
|
|
106
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ringtone?.isLooping = true
|
|
107
|
+
ringtone?.play()
|
|
108
|
+
} catch (e: Exception) { e.printStackTrace() }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fun stopRingtone() {
|
|
112
|
+
try {
|
|
113
|
+
ringtone?.let {
|
|
114
|
+
if (it.isPlaying) it.stop()
|
|
115
|
+
}
|
|
116
|
+
ringtone = null
|
|
117
|
+
} catch (e: Exception) {
|
|
118
|
+
e.printStackTrace()
|
|
119
|
+
ringtone = null
|
|
120
|
+
}
|
|
109
121
|
}
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
// Use the safe call operator ?. to only run if ringtone isn't null
|
|
113
|
-
ringtone?.let {
|
|
114
|
-
if (it.isPlaying) {
|
|
115
|
-
it.stop()
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Always null it out after stopping
|
|
119
|
-
ringtone = null
|
|
120
|
-
} catch (e: Exception) {
|
|
121
|
-
// Prevent the app from crashing if the system Ringtone service is acting up
|
|
122
|
-
e.printStackTrace()
|
|
123
|
-
ringtone = null
|
|
124
|
-
}
|
|
122
|
+
|
|
125
123
|
fun dismissIncomingCall(context: Context, uuid: String?) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
stopRingtone()
|
|
125
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
126
|
+
if (uuid != null) {
|
|
127
|
+
notificationManager.cancel(uuid.hashCode())
|
|
128
|
+
}
|
|
129
129
|
}
|
|
130
130
|
}
|
package/package.json
CHANGED
package/withNativeCallVoip.js
CHANGED
|
@@ -9,7 +9,8 @@ function withMainActivityDataFix(config) {
|
|
|
9
9
|
'import android.view.WindowManager',
|
|
10
10
|
'import android.os.Build',
|
|
11
11
|
'import android.os.Bundle',
|
|
12
|
-
'import android.content.Intent'
|
|
12
|
+
'import android.content.Intent',
|
|
13
|
+
'import android.content.Context' // Added: Required for KEYGUARD_SERVICE
|
|
13
14
|
];
|
|
14
15
|
|
|
15
16
|
imports.forEach(imp => {
|
|
@@ -18,14 +19,6 @@ function withMainActivityDataFix(config) {
|
|
|
18
19
|
}
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
imports.forEach(imp => {
|
|
22
|
-
if (!contents.includes(imp)) {
|
|
23
|
-
contents = contents.replace(/package .*/, (match) => `${match}\n${imp}`);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// FIXED: Corrected Kotlin bitwise OR syntax and logic check
|
|
28
|
-
// We check for "navigatingToCall" so this only runs when Answer is tapped
|
|
29
22
|
const wakeLogic = `super.onCreate(savedInstanceState)
|
|
30
23
|
if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
|
|
31
24
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
@@ -35,13 +28,10 @@ function withMainActivityDataFix(config) {
|
|
|
35
28
|
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
36
29
|
}
|
|
37
30
|
|
|
38
|
-
// Remove Keyguard so we jump straight to app (optional but recommended)
|
|
39
31
|
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as? android.app.KeyguardManager
|
|
40
32
|
keyguardManager?.requestDismissKeyguard(this, null)
|
|
41
33
|
}`;
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
35
|
if (!contents.includes('setShowWhenLocked')) {
|
|
46
36
|
contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogic);
|
|
47
37
|
}
|
|
@@ -67,7 +57,7 @@ function withAndroidConfig(config) {
|
|
|
67
57
|
const androidManifest = config.modResults.manifest;
|
|
68
58
|
const mainApplication = androidManifest.application[0];
|
|
69
59
|
|
|
70
|
-
//
|
|
60
|
+
// Permissions
|
|
71
61
|
const permissions = [
|
|
72
62
|
'android.permission.USE_FULL_SCREEN_INTENT',
|
|
73
63
|
'android.permission.VIBRATE',
|
|
@@ -88,7 +78,7 @@ function withAndroidConfig(config) {
|
|
|
88
78
|
}
|
|
89
79
|
});
|
|
90
80
|
|
|
91
|
-
//
|
|
81
|
+
// Activity Registration: AcceptCallActivity
|
|
92
82
|
mainApplication.activity = mainApplication.activity || [];
|
|
93
83
|
if (!mainApplication.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
|
|
94
84
|
mainApplication.activity.push({
|
|
@@ -96,8 +86,19 @@ function withAndroidConfig(config) {
|
|
|
96
86
|
'android:name': 'com.rnsnativecall.AcceptCallActivity',
|
|
97
87
|
'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
|
|
98
88
|
'android:exported': 'false',
|
|
99
|
-
'android:showWhenLocked': '
|
|
100
|
-
'android:turnScreenOn': '
|
|
89
|
+
'android:showWhenLocked': 'true', // Changed to true for calling
|
|
90
|
+
'android:turnScreenOn': 'true' // Changed to true for calling
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Receiver Registration: CallActionReceiver
|
|
96
|
+
mainApplication.receiver = mainApplication.receiver || [];
|
|
97
|
+
if (!mainApplication.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
|
|
98
|
+
mainApplication.receiver.push({
|
|
99
|
+
$: {
|
|
100
|
+
'android:name': 'com.rnsnativecall.CallActionReceiver',
|
|
101
|
+
'android:exported': 'false'
|
|
101
102
|
}
|
|
102
103
|
});
|
|
103
104
|
}
|
|
@@ -112,7 +113,7 @@ function withAndroidConfig(config) {
|
|
|
112
113
|
});
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
// Headless Task Service (
|
|
116
|
+
// Headless Task Service (Android 14)
|
|
116
117
|
const headlessName = 'com.rnsnativecall.CallHeadlessTask';
|
|
117
118
|
const headlessIndex = mainApplication.service.findIndex(s => s.$['android:name'] === headlessName);
|
|
118
119
|
const headlessEntry = {
|