rns-nativecall 0.4.3 → 0.4.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.
- package/android/build.gradle +3 -0
- package/android/src/main/java/com/rnsnativecall/CallHeadlessTask.kt +1 -2
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +9 -0
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +79 -78
- package/package.json +1 -1
- package/withNativeCallVoip.js +12 -17
package/android/build.gradle
CHANGED
|
@@ -68,4 +68,7 @@ dependencies {
|
|
|
68
68
|
// Glide for profile pictures
|
|
69
69
|
implementation "com.github.bumptech.glide:glide:4.15.1"
|
|
70
70
|
annotationProcessor "com.github.bumptech.glide:compiler:4.15.1"
|
|
71
|
+
|
|
72
|
+
implementation "androidx.core:core-ktx:1.12.0"
|
|
73
|
+
implementation "androidx.appcompat:appcompat:1.6.1"
|
|
71
74
|
}
|
|
@@ -25,8 +25,7 @@ class CallHeadlessTask : HeadlessJsTaskService() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
val notification: Notification = NotificationCompat.Builder(this, channelId)
|
|
28
|
-
.setContentTitle("
|
|
29
|
-
.setSmallIcon(android.R.drawable.sym_call_incoming)
|
|
28
|
+
.setContentTitle("incoming...")
|
|
30
29
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
31
30
|
.build()
|
|
32
31
|
|
|
@@ -15,6 +15,8 @@ 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
|
+
|
|
18
20
|
class CallMessagingService : FirebaseMessagingService() {
|
|
19
21
|
|
|
20
22
|
companion object {
|
|
@@ -60,6 +62,13 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
60
62
|
HeadlessJsTaskService.acquireWakeLockNow(context)
|
|
61
63
|
} catch (e: Exception) { e.printStackTrace() }
|
|
62
64
|
|
|
65
|
+
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
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
|
|
71
|
+
|
|
63
72
|
// 4. Backup Timer
|
|
64
73
|
val showNotificationRunnable = Runnable {
|
|
65
74
|
if (!isAppInForeground(context)) {
|
|
@@ -1,114 +1,115 @@
|
|
|
1
1
|
package com.rnsnativecall
|
|
2
2
|
|
|
3
|
+
import android.app.Notification
|
|
3
4
|
import android.app.NotificationChannel
|
|
4
5
|
import android.app.NotificationManager
|
|
5
6
|
import android.app.PendingIntent
|
|
6
7
|
import android.content.Context
|
|
7
8
|
import android.content.Intent
|
|
9
|
+
import android.graphics.Color
|
|
10
|
+
import android.media.AudioAttributes
|
|
11
|
+
import android.media.RingtoneManager
|
|
8
12
|
import android.os.Build
|
|
9
13
|
import androidx.core.app.NotificationCompat
|
|
10
|
-
import
|
|
11
|
-
import android.
|
|
12
|
-
import android.graphics.Color
|
|
14
|
+
import androidx.core.app.Person
|
|
15
|
+
import android.graphics.drawable.Icon
|
|
13
16
|
|
|
14
17
|
object NativeCallManager {
|
|
15
18
|
|
|
16
|
-
private var ringtone: Ringtone? = null
|
|
17
19
|
const val CALL_CHANNEL_ID = "CALL_CHANNEL_ID"
|
|
18
20
|
|
|
19
21
|
fun handleIncomingPush(context: Context, data: Map<String, String>) {
|
|
20
22
|
val uuid = data["callUuid"] ?: return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
val name = data["name"] ?: "Incoming Call"
|
|
24
|
-
val callType = data["callType"] ?: "audio"
|
|
23
|
+
val name = data["name"] ?: "Unknown Caller"
|
|
24
|
+
val handle = data["handle"] ?: ""
|
|
25
25
|
|
|
26
|
-
val
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
27
|
+
|
|
28
|
+
// 1. Create Channel with High Importance (Required for Heads Up)
|
|
29
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
30
|
+
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
31
|
+
val audioAttributes = AudioAttributes.Builder()
|
|
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)
|
|
30
43
|
}
|
|
31
44
|
|
|
45
|
+
// 2. Prepare Intents
|
|
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.
|
|
32
53
|
val intentToActivity = Intent(context, AcceptCallActivity::class.java).apply {
|
|
33
|
-
action = "
|
|
34
|
-
data.forEach { (
|
|
35
|
-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
54
|
+
action = "ACTION_SHOW_INCOMING_$uuid"
|
|
55
|
+
data.forEach { (k, v) -> putExtra(k, v) }
|
|
36
56
|
}
|
|
57
|
+
val fullScreenPendingIntent = PendingIntent.getActivity(context, uuid.hashCode(), intentToActivity, flags)
|
|
58
|
+
|
|
59
|
+
// B. Answer Intent
|
|
60
|
+
// We point this to MainActivity (or a BroadcastReceiver that launches MainActivity)
|
|
61
|
+
// MainActivity HAS showWhenLocked="true", so it WILL open over the lock screen when tapped.
|
|
62
|
+
val packageName = context.packageName
|
|
63
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) ?: return
|
|
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)
|
|
69
|
+
}
|
|
70
|
+
val answerPendingIntent = PendingIntent.getActivity(context, uuid.hashCode() + 1, launchIntent, flags)
|
|
37
71
|
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
// C. Decline Intent
|
|
40
73
|
val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
|
|
41
|
-
action = "
|
|
42
|
-
putExtra("
|
|
43
|
-
data.forEach { (key, value) -> putExtra(key, value) }
|
|
74
|
+
action = "ACTION_REJECT_CALL"
|
|
75
|
+
putExtra("callUuid", uuid)
|
|
44
76
|
}
|
|
77
|
+
val rejectPendingIntent = PendingIntent.getBroadcast(context, uuid.hashCode() + 2, rejectIntent, flags)
|
|
45
78
|
|
|
46
|
-
|
|
47
|
-
val
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
52
|
-
val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Call", NotificationManager.IMPORTANCE_HIGH).apply {
|
|
53
|
-
setBypassDnd(true)
|
|
54
|
-
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
|
55
|
-
enableVibration(true) //can be removed
|
|
56
|
-
setSound(null, null)
|
|
57
|
-
}
|
|
58
|
-
notificationManager.createNotificationChannel(channel)
|
|
59
|
-
}
|
|
79
|
+
// 3. Build Notification using CallStyle (Android 12+) or Custom (Older)
|
|
80
|
+
val person = Person.Builder()
|
|
81
|
+
.setName(name)
|
|
82
|
+
.setKey(uuid)
|
|
83
|
+
.build()
|
|
60
84
|
|
|
61
85
|
val builder = NotificationCompat.Builder(context, CALL_CHANNEL_ID)
|
|
62
86
|
.setSmallIcon(context.applicationInfo.icon)
|
|
63
|
-
.
|
|
64
|
-
.setContentText(name)
|
|
65
|
-
|
|
66
|
-
.setPriority(NotificationCompat.PRIORITY_HIGH) //old was PRIORITY_MAX
|
|
87
|
+
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
67
88
|
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
68
|
-
.setOngoing(true)
|
|
69
89
|
.setAutoCancel(false)
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
.
|
|
90
|
+
.setOngoing(true)
|
|
91
|
+
.setColor(Color.parseColor("#00C853"))
|
|
92
|
+
// Crucial: Set the full screen intent, but let the Manifest block the auto-launch
|
|
93
|
+
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
94
|
+
|
|
95
|
+
// Apply Native Call Style (The "Pill")
|
|
96
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
97
|
+
builder.setStyle(
|
|
98
|
+
NotificationCompat.CallStyle.forIncomingCall(person, rejectPendingIntent, answerPendingIntent)
|
|
99
|
+
)
|
|
100
|
+
} else {
|
|
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
|
+
}
|
|
74
107
|
|
|
75
108
|
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
79
|
-
ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
|
|
80
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ringtone?.isLooping = true
|
|
81
|
-
ringtone?.play()
|
|
82
|
-
} catch (e: Exception) { e.printStackTrace() }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
fun stopRingtone() {
|
|
86
|
-
try {
|
|
87
|
-
// Use the safe call operator ?. to only run if ringtone isn't null
|
|
88
|
-
ringtone?.let {
|
|
89
|
-
if (it.isPlaying) {
|
|
90
|
-
it.stop()
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// Always null it out after stopping
|
|
94
|
-
ringtone = null
|
|
95
|
-
} catch (e: Exception) {
|
|
96
|
-
// Prevent the app from crashing if the system Ringtone service is acting up
|
|
97
|
-
e.printStackTrace()
|
|
98
|
-
ringtone = null
|
|
99
109
|
}
|
|
100
|
-
}
|
|
101
110
|
|
|
102
|
-
fun dismissIncomingCall(context: Context, uuid: String?) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (uuid != null) {
|
|
107
|
-
notificationManager.cancel(uuid.hashCode())
|
|
108
|
-
} else {
|
|
109
|
-
// Fallback: If for some reason uuid is null, cancel everything
|
|
110
|
-
// (Optional: only if you want a safety net)
|
|
111
|
-
// notificationManager.cancelAll()
|
|
112
|
-
}
|
|
111
|
+
fun dismissIncomingCall(context: Context, uuid: String?) {
|
|
112
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
113
|
+
if (uuid != null) notificationManager.cancel(uuid.hashCode())
|
|
113
114
|
}
|
|
114
115
|
}
|
package/package.json
CHANGED
package/withNativeCallVoip.js
CHANGED
|
@@ -20,14 +20,16 @@ function withMainActivityDataFix(config) {
|
|
|
20
20
|
|
|
21
21
|
// FIXED: Corrected Kotlin bitwise OR syntax and logic check
|
|
22
22
|
const wakeLogic = `super.onCreate(savedInstanceState)
|
|
23
|
-
if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
23
|
+
// if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
|
|
24
|
+
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
25
|
+
// setShowWhenLocked(true)
|
|
26
|
+
// setTurnScreenOn(true)
|
|
27
|
+
// } else {
|
|
28
|
+
// window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
29
|
+
// }
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
`;
|
|
31
33
|
|
|
32
34
|
if (!contents.includes('setShowWhenLocked')) {
|
|
33
35
|
contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogic);
|
|
@@ -54,13 +56,6 @@ function withAndroidConfig(config) {
|
|
|
54
56
|
const androidManifest = config.modResults.manifest;
|
|
55
57
|
const mainApplication = androidManifest.application[0];
|
|
56
58
|
|
|
57
|
-
// 1. MainActivity flags
|
|
58
|
-
const mainActivity = mainApplication.activity.find(a => a.$["android:name"] === ".MainActivity");
|
|
59
|
-
if (mainActivity) {
|
|
60
|
-
mainActivity.$["android:showWhenLocked"] = "true";
|
|
61
|
-
mainActivity.$["android:turnScreenOn"] = "true";
|
|
62
|
-
}
|
|
63
|
-
|
|
64
59
|
// 2. Permissions
|
|
65
60
|
const permissions = [
|
|
66
61
|
'android.permission.USE_FULL_SCREEN_INTENT',
|
|
@@ -90,8 +85,8 @@ function withAndroidConfig(config) {
|
|
|
90
85
|
'android:name': 'com.rnsnativecall.AcceptCallActivity',
|
|
91
86
|
'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
|
|
92
87
|
'android:exported': 'false',
|
|
93
|
-
'android:showWhenLocked': '
|
|
94
|
-
'android:turnScreenOn': '
|
|
88
|
+
'android:showWhenLocked': 'false',
|
|
89
|
+
'android:turnScreenOn': 'false'
|
|
95
90
|
}
|
|
96
91
|
});
|
|
97
92
|
}
|