rns-nativecall 0.3.3 → 0.3.5
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.
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package com.rnsnativecall
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import com.facebook.react.HeadlessJsTaskService
|
|
5
|
+
import com.facebook.react.bridge.Arguments
|
|
6
|
+
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
|
7
|
+
|
|
8
|
+
class CallHeadlessTask : HeadlessJsTaskService() {
|
|
9
|
+
override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? {
|
|
10
|
+
val extras = intent.extras
|
|
11
|
+
return if (extras != null) {
|
|
12
|
+
// INCREASE timeout to 30000 (30s) so it covers your 18s delay
|
|
13
|
+
HeadlessJsTaskConfig(
|
|
14
|
+
"ColdStartCallTask",
|
|
15
|
+
Arguments.fromBundle(extras),
|
|
16
|
+
30000,
|
|
17
|
+
true // Allow in foreground
|
|
18
|
+
)
|
|
19
|
+
} else {
|
|
20
|
+
null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -3,55 +3,103 @@ package com.rnsnativecall
|
|
|
3
3
|
import android.app.ActivityManager
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.content.Intent
|
|
6
|
+
import android.os.Bundle
|
|
6
7
|
import android.os.Handler
|
|
7
8
|
import android.os.Looper
|
|
9
|
+
import android.app.NotificationManager
|
|
10
|
+
import androidx.core.app.NotificationCompat
|
|
8
11
|
import com.google.firebase.messaging.FirebaseMessagingService
|
|
9
12
|
import com.google.firebase.messaging.RemoteMessage
|
|
13
|
+
import com.facebook.react.HeadlessJsTaskService
|
|
10
14
|
|
|
11
15
|
class CallMessagingService : FirebaseMessagingService() {
|
|
12
16
|
|
|
17
|
+
// This companion object allows the Cancel logic to find the active timer
|
|
18
|
+
companion object {
|
|
19
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
20
|
+
private val pendingNotifications = mutableMapOf<String, Runnable>()
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
|
14
24
|
val data = remoteMessage.data
|
|
15
25
|
val context = applicationContext
|
|
16
26
|
|
|
27
|
+
// Extract variables for logic
|
|
28
|
+
val uuid = data["callUuid"] ?: return
|
|
29
|
+
val type = data["type"] ?: ""
|
|
30
|
+
|
|
31
|
+
// CASE 1: CALLER HUNG UP / CANCELLED
|
|
32
|
+
if (type == "CANCEL") {
|
|
33
|
+
// 1. Remove the pending 18s timer for this specific call
|
|
34
|
+
pendingNotifications[uuid]?.let {
|
|
35
|
+
handler.removeCallbacks(it)
|
|
36
|
+
pendingNotifications.remove(uuid)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. Stop the ringtone/Pill if it had already started showing
|
|
40
|
+
NativeCallManager.stopRingtone()
|
|
41
|
+
CallHandler.destroyNativeCallUI(uuid) // Ensure native UI is killed
|
|
42
|
+
|
|
43
|
+
// 3. Show standard "Missed Call" notification
|
|
44
|
+
showMissedCallNotification(context, data, uuid)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// CASE 2: NEW INCOMING CALL
|
|
17
49
|
if (isAppInForeground(context)) {
|
|
50
|
+
// App is visible: Send event to active JS bridge immediately
|
|
18
51
|
CallModule.sendEventToJS("onCallReceived", data)
|
|
19
52
|
} else {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
// 1. Wake the actual App process by launching the Intent
|
|
54
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
55
|
+
launchIntent?.apply {
|
|
56
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
57
|
+
// This is key: it tells the app to boot the JS but stay minimized
|
|
58
|
+
putExtra("background_wake", true)
|
|
59
|
+
}
|
|
60
|
+
context.startActivity(launchIntent)
|
|
61
|
+
|
|
62
|
+
// You SHOULD see "Android Bundled..." now because the Activity is starting
|
|
63
|
+
|
|
64
|
+
val showNotificationRunnable = Runnable {
|
|
65
|
+
if (!isAppInForeground(context)) {
|
|
66
|
+
NativeCallManager.handleIncomingPush(context, data)
|
|
34
67
|
}
|
|
35
68
|
}
|
|
69
|
+
handler.postDelayed(showNotificationRunnable, 18000)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
36
72
|
|
|
37
|
-
private fun
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
private fun startHeadlessTask(context: Context, data: Map<String, String>) {
|
|
74
|
+
val serviceIntent = Intent(context, CallHeadlessTask::class.java)
|
|
75
|
+
val bundle = Bundle()
|
|
76
|
+
data.forEach { (key, value) -> bundle.putString(key, value) }
|
|
77
|
+
serviceIntent.putExtras(bundle)
|
|
78
|
+
|
|
79
|
+
context.startService(serviceIntent)
|
|
80
|
+
HeadlessJsTaskService.acquireWakeLockNow(context)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private fun showMissedCallNotification(context: Context, data: Map<String, String>, uuid: String) {
|
|
84
|
+
val name = data["name"] ?: "Unknown"
|
|
85
|
+
val callType = data["callType"] ?: "video"
|
|
86
|
+
val channelId = "missed_calls" // Ensure this channel is created in your MainApplication or CallManager
|
|
87
|
+
|
|
88
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
89
|
+
|
|
90
|
+
// Use ic_launcher as fallback if ic_missed_call isn't found
|
|
91
|
+
var iconResId = context.resources.getIdentifier("ic_missed_call", "drawable", context.packageName)
|
|
92
|
+
if (iconResId == 0) iconResId = android.R.drawable.sym_call_missed
|
|
93
|
+
|
|
94
|
+
val builder = NotificationCompat.Builder(context, channelId)
|
|
95
|
+
.setSmallIcon(iconResId)
|
|
96
|
+
.setContentTitle("Missed $callType call")
|
|
97
|
+
.setContentText("You missed a call from $name")
|
|
98
|
+
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
99
|
+
.setAutoCancel(true)
|
|
100
|
+
.setCategory(NotificationCompat.CATEGORY_MISS)
|
|
101
|
+
|
|
102
|
+
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
55
103
|
}
|
|
56
104
|
|
|
57
105
|
private fun isAppInForeground(context: Context): Boolean {
|
|
@@ -28,7 +28,6 @@ object NativeCallManager {
|
|
|
28
28
|
} else {
|
|
29
29
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
30
30
|
}
|
|
31
|
-
|
|
32
31
|
// 1. IMPROVED INTENT: Direct to AcceptCallActivity
|
|
33
32
|
// This is better than a dummy intent because it allows the OS to
|
|
34
33
|
// launch your "Accept" logic immediately if the phone is locked.
|
package/package.json
CHANGED
package/withNativeCallVoip.js
CHANGED
|
@@ -5,10 +5,15 @@ function withMainActivityDataFix(config) {
|
|
|
5
5
|
return withMainActivity(config, (config) => {
|
|
6
6
|
let contents = config.modResults.contents;
|
|
7
7
|
|
|
8
|
+
// 1. Ensure necessary Imports are present
|
|
8
9
|
if (!contents.includes('import android.content.Intent')) {
|
|
9
10
|
contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
|
|
10
11
|
}
|
|
12
|
+
if (!contents.includes('import android.os.Bundle')) {
|
|
13
|
+
contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.os.Bundle`);
|
|
14
|
+
}
|
|
11
15
|
|
|
16
|
+
// 2. Define the code blocks
|
|
12
17
|
const onNewIntentCode = `
|
|
13
18
|
override fun onNewIntent(intent: Intent) {
|
|
14
19
|
super.onNewIntent(intent)
|
|
@@ -16,11 +21,28 @@ function withMainActivityDataFix(config) {
|
|
|
16
21
|
}
|
|
17
22
|
`;
|
|
18
23
|
|
|
24
|
+
const onCreateCode = `
|
|
25
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
26
|
+
super.onCreate(savedInstanceState)
|
|
27
|
+
// If woken up silently, move to back to prevent UI flicker
|
|
28
|
+
if (intent.getBooleanExtra("background_wake", false)) {
|
|
29
|
+
moveTaskToBack(true)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
// 3. Insert the codes before the last closing brace of the class
|
|
19
35
|
if (!contents.includes('override fun onNewIntent')) {
|
|
20
36
|
const lastBraceIndex = contents.lastIndexOf('}');
|
|
21
37
|
contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
|
|
22
38
|
}
|
|
23
39
|
|
|
40
|
+
if (!contents.includes('override fun onCreate')) {
|
|
41
|
+
// Re-calculate lastBraceIndex because contents string has changed
|
|
42
|
+
const lastBraceIndex = contents.lastIndexOf('}');
|
|
43
|
+
contents = contents.slice(0, lastBraceIndex) + onCreateCode + contents.slice(lastBraceIndex);
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
config.modResults.contents = contents;
|
|
25
47
|
return config;
|
|
26
48
|
});
|
|
@@ -50,37 +72,29 @@ function withAndroidConfig(config) {
|
|
|
50
72
|
}
|
|
51
73
|
});
|
|
52
74
|
|
|
53
|
-
// 2. Activity
|
|
75
|
+
// 2. Activity Setup
|
|
54
76
|
application.activity = application.activity || [];
|
|
55
77
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
'android:showWhenLocked': 'true',
|
|
72
|
-
'android:turnScreenOn': 'true'
|
|
73
|
-
}
|
|
74
|
-
});
|
|
78
|
+
// Add AcceptCallActivity (The Trampoline)
|
|
79
|
+
if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
|
|
80
|
+
application.activity.push({
|
|
81
|
+
$: {
|
|
82
|
+
'android:name': 'com.rnsnativecall.AcceptCallActivity',
|
|
83
|
+
'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
|
|
84
|
+
'android:excludeFromRecents': 'true',
|
|
85
|
+
'android:noHistory': 'true',
|
|
86
|
+
'android:exported': 'false',
|
|
87
|
+
'android:launchMode': 'singleInstance',
|
|
88
|
+
'android:showWhenLocked': 'true',
|
|
89
|
+
'android:turnScreenOn': 'true'
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
75
93
|
|
|
76
|
-
// 3. Service
|
|
94
|
+
// 3. Service Setup (Firebase + Headless Task)
|
|
77
95
|
application.service = application.service || [];
|
|
78
96
|
|
|
79
|
-
//
|
|
80
|
-
application.service = application.service.filter(
|
|
81
|
-
s => s.$['android:name'] !== 'com.rnsnativecall.MyConnectionService'
|
|
82
|
-
);
|
|
83
|
-
|
|
97
|
+
// Add Firebase Messaging Service
|
|
84
98
|
const firebaseServiceName = 'com.rnsnativecall.CallMessagingService';
|
|
85
99
|
if (!application.service.some(s => s.$['android:name'] === firebaseServiceName)) {
|
|
86
100
|
application.service.push({
|
|
@@ -92,6 +106,17 @@ function withAndroidConfig(config) {
|
|
|
92
106
|
});
|
|
93
107
|
}
|
|
94
108
|
|
|
109
|
+
// Add Headless JS Task Service
|
|
110
|
+
const headlessServiceName = 'com.rnsnativecall.CallHeadlessTask';
|
|
111
|
+
if (!application.service.some(s => s.$['android:name'] === headlessServiceName)) {
|
|
112
|
+
application.service.push({
|
|
113
|
+
$: {
|
|
114
|
+
'android:name': headlessServiceName,
|
|
115
|
+
'android:exported': 'false'
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
95
120
|
// 4. Receiver Setup
|
|
96
121
|
application.receiver = application.receiver || [];
|
|
97
122
|
const receiverName = 'com.rnsnativecall.CallActionReceiver';
|
|
@@ -101,6 +126,8 @@ function withAndroidConfig(config) {
|
|
|
101
126
|
});
|
|
102
127
|
}
|
|
103
128
|
|
|
129
|
+
|
|
130
|
+
|
|
104
131
|
return config;
|
|
105
132
|
});
|
|
106
133
|
}
|