rns-nativecall 1.0.7 → 1.0.9
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 +11 -6
- package/android/src/main/java/com/rnsnativecall/CallForegroundService.kt +9 -106
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +5 -8
- package/android/src/main/java/com/rnsnativecall/CallModule.kt +91 -61
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +27 -104
- package/android/src/main/java/com/rnsnativecall/UnlockReceiver.kt +13 -11
- package/package.json +1 -1
- package/withNativeCallVoip.js +4 -5
|
@@ -2,20 +2,25 @@ package com.rnsnativecall
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.content.Intent
|
|
5
|
+
import android.os.Build
|
|
5
6
|
import android.os.Bundle
|
|
6
7
|
import android.view.WindowManager
|
|
7
|
-
import android.os.Build
|
|
8
8
|
|
|
9
9
|
class AcceptCallActivity : Activity() {
|
|
10
10
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
11
11
|
super.onCreate(savedInstanceState)
|
|
12
|
-
|
|
12
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
13
|
+
setShowWhenLocked(true)
|
|
14
|
+
setTurnScreenOn(true)
|
|
15
|
+
}
|
|
13
16
|
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
14
17
|
if (launchIntent != null) {
|
|
15
18
|
launchIntent.apply {
|
|
16
|
-
addFlags(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
addFlags(
|
|
20
|
+
Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
21
|
+
Intent.FLAG_ACTIVITY_SINGLE_TOP or
|
|
22
|
+
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT,
|
|
23
|
+
)
|
|
19
24
|
putExtras(intent.extras ?: Bundle())
|
|
20
25
|
putExtra("navigatingToCall", true)
|
|
21
26
|
}
|
|
@@ -25,4 +30,4 @@ class AcceptCallActivity : Activity() {
|
|
|
25
30
|
finish()
|
|
26
31
|
overridePendingTransition(0, 0)
|
|
27
32
|
}
|
|
28
|
-
}
|
|
33
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
package com.rnsnativecall
|
|
2
2
|
|
|
3
3
|
import android.app.*
|
|
4
|
-
import android.app.Service
|
|
5
|
-
import android.content.BroadcastReceiver
|
|
6
4
|
import android.content.Context
|
|
7
5
|
import android.content.Intent
|
|
8
6
|
import android.content.IntentFilter
|
|
@@ -13,33 +11,22 @@ import android.os.Handler
|
|
|
13
11
|
import android.os.IBinder
|
|
14
12
|
import android.os.Looper
|
|
15
13
|
import androidx.core.app.NotificationCompat
|
|
16
|
-
import androidx.core.content.ContextCompat
|
|
17
14
|
import com.facebook.react.HeadlessJsTaskService
|
|
18
15
|
|
|
19
16
|
class CallForegroundService : Service() {
|
|
20
|
-
private var unlockReceiver: UnlockReceiver? = null
|
|
17
|
+
private var unlockReceiver: UnlockReceiver? = null
|
|
21
18
|
|
|
22
19
|
companion object {
|
|
23
|
-
private const val CHANNEL_ID = "CALL_WAKE_SILENT"
|
|
24
|
-
|
|
25
20
|
fun stop(context: Context) {
|
|
26
|
-
|
|
27
|
-
context.stopService(intent)
|
|
21
|
+
context.stopService(Intent(context, CallForegroundService::class.java))
|
|
28
22
|
}
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
override fun onCreate() {
|
|
32
26
|
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
27
|
unlockReceiver = UnlockReceiver()
|
|
38
28
|
val filter = IntentFilter(Intent.ACTION_USER_PRESENT)
|
|
39
|
-
|
|
40
29
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
41
|
-
// Prefer NOT_EXPORTED for dynamically-registered receivers to avoid
|
|
42
|
-
// background delivery issues and to restrict broadcasts to our process.
|
|
43
30
|
registerReceiver(unlockReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
|
44
31
|
} else {
|
|
45
32
|
registerReceiver(unlockReceiver, filter)
|
|
@@ -52,43 +39,10 @@ class CallForegroundService : Service() {
|
|
|
52
39
|
startId: Int,
|
|
53
40
|
): Int {
|
|
54
41
|
val data = intent?.extras
|
|
55
|
-
val name = data?.getString("name") ?: "Someone"
|
|
56
|
-
val uuid = data?.getString("callUuid") ?: "default_uuid"
|
|
57
|
-
|
|
58
|
-
val notificationId = uuid.hashCode()
|
|
59
|
-
|
|
60
|
-
createNotificationChannel()
|
|
61
|
-
|
|
62
|
-
val notification =
|
|
63
|
-
NotificationCompat
|
|
64
|
-
.Builder(this, CHANNEL_ID)
|
|
65
|
-
.setContentTitle(name)
|
|
66
|
-
.setContentText("Connecting...")
|
|
67
|
-
.setSmallIcon(applicationInfo.icon)
|
|
68
|
-
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
69
|
-
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
70
|
-
.setOngoing(true)
|
|
71
|
-
.build()
|
|
72
|
-
|
|
73
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
74
|
-
startForeground(
|
|
75
|
-
notificationId,
|
|
76
|
-
notification,
|
|
77
|
-
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
|
|
78
|
-
)
|
|
79
|
-
} else {
|
|
80
|
-
startForeground(notificationId, notification)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Launch the Headless Task
|
|
84
42
|
val headlessIntent =
|
|
85
43
|
Intent(this, CallHeadlessTask::class.java).apply {
|
|
86
44
|
val bundle = Bundle()
|
|
87
|
-
data?.let { b ->
|
|
88
|
-
for (key in b.keySet()) {
|
|
89
|
-
bundle.putString(key, b.get(key)?.toString())
|
|
90
|
-
}
|
|
91
|
-
}
|
|
45
|
+
data?.let { b -> for (key in b.keySet()) bundle.putString(key, b.get(key)?.toString()) }
|
|
92
46
|
putExtras(bundle)
|
|
93
47
|
}
|
|
94
48
|
|
|
@@ -99,52 +53,18 @@ class CallForegroundService : Service() {
|
|
|
99
53
|
e.printStackTrace()
|
|
100
54
|
}
|
|
101
55
|
|
|
102
|
-
|
|
103
|
-
Handler(Looper.getMainLooper()).postDelayed({
|
|
104
|
-
try {
|
|
105
|
-
stopSelf()
|
|
106
|
-
} catch (e: Exception) {
|
|
107
|
-
}
|
|
108
|
-
}, 30000)
|
|
109
|
-
|
|
56
|
+
Handler(Looper.getMainLooper()).postDelayed({ stopSelf() }, 30000)
|
|
110
57
|
return START_NOT_STICKY
|
|
111
58
|
}
|
|
112
59
|
|
|
113
60
|
override fun onDestroy() {
|
|
114
|
-
// --- CLEANUP ---
|
|
115
|
-
// Unregister to prevent memory leaks once the call ends or service stops
|
|
116
61
|
try {
|
|
117
62
|
unlockReceiver?.let { unregisterReceiver(it) }
|
|
118
63
|
} catch (e: Exception) {
|
|
119
|
-
e.printStackTrace()
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
stopForeground(true)
|
|
124
|
-
} catch (_: Exception) {
|
|
125
64
|
}
|
|
126
65
|
super.onDestroy()
|
|
127
66
|
}
|
|
128
67
|
|
|
129
|
-
private fun createNotificationChannel() {
|
|
130
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
131
|
-
val channel =
|
|
132
|
-
NotificationChannel(
|
|
133
|
-
CHANNEL_ID,
|
|
134
|
-
"Call Service",
|
|
135
|
-
NotificationManager.IMPORTANCE_LOW,
|
|
136
|
-
).apply {
|
|
137
|
-
description = "Incoming call connection state"
|
|
138
|
-
setSound(null, null)
|
|
139
|
-
enableVibration(false)
|
|
140
|
-
setBypassDnd(false)
|
|
141
|
-
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
|
142
|
-
}
|
|
143
|
-
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
144
|
-
manager.createNotificationChannel(channel)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
68
|
override fun onBind(intent: Intent?): IBinder? = null
|
|
149
69
|
}
|
|
150
70
|
|
|
@@ -154,36 +74,19 @@ class CallUiForegroundService : Service() {
|
|
|
154
74
|
flags: Int,
|
|
155
75
|
startId: Int,
|
|
156
76
|
): Int {
|
|
157
|
-
val notification =
|
|
158
|
-
|
|
159
|
-
?: run {
|
|
160
|
-
stopSelf()
|
|
161
|
-
return START_NOT_STICKY
|
|
162
|
-
}
|
|
77
|
+
val notification = NativeCallManager.pendingCallNotification ?: return START_NOT_STICKY
|
|
78
|
+
val id = NativeCallManager.pendingNotificationId ?: return START_NOT_STICKY
|
|
163
79
|
|
|
164
|
-
val notificationId =
|
|
165
|
-
NativeCallManager.pendingNotificationId
|
|
166
|
-
?: run {
|
|
167
|
-
stopSelf()
|
|
168
|
-
return START_NOT_STICKY
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Inside CallUiForegroundService.onStartCommand
|
|
172
80
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
173
|
-
startForeground(
|
|
174
|
-
notificationId,
|
|
175
|
-
notification,
|
|
176
|
-
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL,
|
|
177
|
-
)
|
|
81
|
+
startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL)
|
|
178
82
|
} else {
|
|
179
|
-
startForeground(
|
|
83
|
+
startForeground(id, notification)
|
|
180
84
|
}
|
|
181
|
-
|
|
182
85
|
return START_NOT_STICKY
|
|
183
86
|
}
|
|
184
87
|
|
|
185
88
|
override fun onDestroy() {
|
|
186
|
-
stopForeground(
|
|
89
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) stopForeground(STOP_FOREGROUND_REMOVE)
|
|
187
90
|
super.onDestroy()
|
|
188
91
|
}
|
|
189
92
|
|
|
@@ -24,7 +24,6 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
24
24
|
val type = data["type"] ?: ""
|
|
25
25
|
|
|
26
26
|
if (type == "CANCEL") {
|
|
27
|
-
NativeCallManager.stopRingtone()
|
|
28
27
|
// Pass context here to persist the cancellation
|
|
29
28
|
CallState.markCanceled(uuid, context)
|
|
30
29
|
|
|
@@ -57,10 +56,10 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
57
56
|
// Foreground → send event directly
|
|
58
57
|
CallModule.sendEventToJS("onCallReceived", data)
|
|
59
58
|
} else {
|
|
60
|
-
// Background → start
|
|
59
|
+
// Background → start the SILENT wake service
|
|
61
60
|
val serviceIntent =
|
|
62
61
|
Intent(context, CallForegroundService::class.java).apply {
|
|
63
|
-
putExtra("callUuid", uuid)
|
|
62
|
+
putExtra("callUuid", uuid)
|
|
64
63
|
putExtras(
|
|
65
64
|
Bundle().apply {
|
|
66
65
|
data.forEach { (k, v) -> putString(k, v) }
|
|
@@ -68,11 +67,9 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
68
67
|
)
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
context.startService(serviceIntent)
|
|
75
|
-
}
|
|
70
|
+
// FIX: Always use startService here now.
|
|
71
|
+
// We are no longer making this a "Foreground Service" at the wake stage.
|
|
72
|
+
context.startService(serviceIntent)
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
75
|
|
|
@@ -2,16 +2,17 @@ package com.rnsnativecall
|
|
|
2
2
|
|
|
3
3
|
import android.app.NotificationManager
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import com.facebook.react.bridge.*
|
|
6
|
-
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
7
5
|
import android.content.Intent
|
|
6
|
+
import android.net.Uri
|
|
8
7
|
import android.os.Build
|
|
9
8
|
import android.provider.Settings
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
9
|
+
import com.facebook.react.bridge.*
|
|
10
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
class CallModule(
|
|
13
|
+
reactContext: ReactApplicationContext,
|
|
14
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
15
|
+
init {
|
|
15
16
|
instance = this
|
|
16
17
|
notifyReady()
|
|
17
18
|
}
|
|
@@ -40,20 +41,26 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
@ReactMethod
|
|
43
|
-
fun displayIncomingCall(
|
|
44
|
+
fun displayIncomingCall(
|
|
45
|
+
uuid: String,
|
|
46
|
+
name: String,
|
|
47
|
+
callType: String,
|
|
48
|
+
promise: Promise,
|
|
49
|
+
) {
|
|
44
50
|
try {
|
|
45
|
-
val data =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
val data =
|
|
52
|
+
mapOf(
|
|
53
|
+
"callUuid" to uuid,
|
|
54
|
+
"name" to name,
|
|
55
|
+
"callType" to callType,
|
|
56
|
+
)
|
|
50
57
|
NativeCallManager.handleIncomingPush(reactApplicationContext, data)
|
|
51
58
|
promise.resolve(true)
|
|
52
59
|
} catch (e: Exception) {
|
|
53
60
|
promise.reject("CALL_ERROR", e.message)
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
|
-
|
|
63
|
+
|
|
57
64
|
// Inside your CallModule class
|
|
58
65
|
@ReactMethod
|
|
59
66
|
fun checkOverlayPermission(promise: Promise) {
|
|
@@ -64,34 +71,39 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
@ReactMethod
|
|
75
|
+
fun requestOverlayPermission() {
|
|
76
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
77
|
+
if (!Settings.canDrawOverlays(reactApplicationContext)) {
|
|
78
|
+
val intent =
|
|
79
|
+
Intent(
|
|
80
|
+
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
|
81
|
+
Uri.parse("package:${reactApplicationContext.packageName}"),
|
|
82
|
+
)
|
|
83
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
84
|
+
reactApplicationContext.startActivity(intent)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
80
88
|
|
|
81
89
|
/**
|
|
82
|
-
* Combined Validity Check:
|
|
90
|
+
* Combined Validity Check:
|
|
83
91
|
* Used by Headless Task to see if it should proceed with the UI.
|
|
84
92
|
*/
|
|
85
93
|
@ReactMethod
|
|
86
|
-
fun checkCallValidity(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
fun checkCallValidity(
|
|
95
|
+
uuid: String,
|
|
96
|
+
promise: Promise,
|
|
97
|
+
) {
|
|
98
|
+
// Pass context so it can check the persistent disk storage
|
|
99
|
+
val isValid = CallState.shouldProceed(uuid, reactApplicationContext)
|
|
100
|
+
val isCanceled = CallState.isCanceled(uuid, reactApplicationContext)
|
|
101
|
+
|
|
102
|
+
val map =
|
|
103
|
+
Arguments.createMap().apply {
|
|
104
|
+
putBoolean("isValid", isValid)
|
|
105
|
+
putBoolean("isCanceled", isCanceled)
|
|
106
|
+
}
|
|
95
107
|
promise.resolve(map)
|
|
96
108
|
}
|
|
97
109
|
|
|
@@ -112,29 +124,34 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
112
124
|
* Useful for checking if the UI is still relevant.
|
|
113
125
|
*/
|
|
114
126
|
@ReactMethod
|
|
115
|
-
fun checkCallStatus(
|
|
127
|
+
fun checkCallStatus(
|
|
128
|
+
uuid: String,
|
|
129
|
+
promise: Promise,
|
|
130
|
+
) {
|
|
116
131
|
val isCanceled = CallState.isCanceled(uuid)
|
|
117
132
|
val isCurrent = CallState.getCurrent() == uuid
|
|
118
|
-
|
|
119
|
-
val map =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
|
|
134
|
+
val map =
|
|
135
|
+
Arguments.createMap().apply {
|
|
136
|
+
putBoolean("isCanceled", isCanceled)
|
|
137
|
+
putBoolean("isActive", isCurrent)
|
|
138
|
+
putBoolean("shouldDisplay", isCurrent && !isCanceled)
|
|
139
|
+
}
|
|
124
140
|
promise.resolve(map)
|
|
125
141
|
}
|
|
126
142
|
|
|
127
143
|
@ReactMethod
|
|
128
144
|
fun endNativeCall(uuid: String) {
|
|
129
|
-
NativeCallManager.
|
|
145
|
+
NativeCallManager.dismissIncomingCall(this, uuid)
|
|
130
146
|
CallState.clear(uuid)
|
|
131
147
|
pendingCallDataMap = null
|
|
132
|
-
|
|
148
|
+
|
|
133
149
|
val notificationManager = reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
134
150
|
notificationManager.cancel(101)
|
|
135
151
|
}
|
|
136
152
|
|
|
137
153
|
@ReactMethod fun addListener(eventName: String) {}
|
|
154
|
+
|
|
138
155
|
@ReactMethod fun removeListeners(count: Int) {}
|
|
139
156
|
|
|
140
157
|
companion object {
|
|
@@ -149,14 +166,17 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
149
166
|
return reactContext != null && reactContext.hasActiveCatalystInstance()
|
|
150
167
|
}
|
|
151
168
|
|
|
152
|
-
@JvmStatic
|
|
169
|
+
@JvmStatic
|
|
153
170
|
fun setPendingCallData(data: Map<String, String>) {
|
|
154
|
-
|
|
171
|
+
pendingCallDataMap = data
|
|
155
172
|
}
|
|
156
173
|
|
|
157
|
-
@JvmStatic
|
|
158
|
-
fun setPendingCallData(
|
|
159
|
-
|
|
174
|
+
@JvmStatic
|
|
175
|
+
fun setPendingCallData(
|
|
176
|
+
eventName: String,
|
|
177
|
+
data: Map<String, String>,
|
|
178
|
+
) {
|
|
179
|
+
pendingEvents[eventName] = data
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
@JvmStatic
|
|
@@ -174,23 +194,33 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
174
194
|
}
|
|
175
195
|
|
|
176
196
|
@JvmStatic
|
|
177
|
-
fun setPendingEvent(
|
|
197
|
+
fun setPendingEvent(
|
|
198
|
+
eventName: String,
|
|
199
|
+
data: Map<String, String>,
|
|
200
|
+
) {
|
|
178
201
|
pendingEvents[eventName] = data
|
|
179
202
|
}
|
|
180
203
|
|
|
181
204
|
@JvmStatic
|
|
182
|
-
fun sendEventToJS(
|
|
205
|
+
fun sendEventToJS(
|
|
206
|
+
eventName: String,
|
|
207
|
+
params: Any?,
|
|
208
|
+
) {
|
|
183
209
|
val reactContext = instance?.reactApplicationContext
|
|
184
|
-
val bridgeData =
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
210
|
+
val bridgeData =
|
|
211
|
+
when (params) {
|
|
212
|
+
is Map<*, *> -> {
|
|
213
|
+
val map = Arguments.createMap()
|
|
214
|
+
params.forEach { (key, value) ->
|
|
215
|
+
map.putString(key.toString(), value.toString())
|
|
216
|
+
}
|
|
217
|
+
map
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
else -> {
|
|
221
|
+
null
|
|
189
222
|
}
|
|
190
|
-
map
|
|
191
223
|
}
|
|
192
|
-
else -> null
|
|
193
|
-
}
|
|
194
224
|
|
|
195
225
|
if (isReady()) {
|
|
196
226
|
reactContext
|
|
@@ -202,4 +232,4 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
202
232
|
}
|
|
203
233
|
}
|
|
204
234
|
}
|
|
205
|
-
}
|
|
235
|
+
}
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
package com.rnsnativecall
|
|
2
2
|
|
|
3
|
-
import android.app
|
|
4
|
-
import android.app.NotificationChannel
|
|
5
|
-
import android.app.NotificationManager
|
|
6
|
-
import android.app.PendingIntent
|
|
3
|
+
import android.app.*
|
|
7
4
|
import android.content.Context
|
|
8
5
|
import android.content.Intent
|
|
9
|
-
import android.content.pm.ServiceInfo
|
|
10
6
|
import android.graphics.Color
|
|
11
7
|
import android.media.AudioAttributes
|
|
12
8
|
import android.media.RingtoneManager
|
|
13
9
|
import android.os.Build
|
|
14
|
-
import android.os.Bundle
|
|
15
10
|
import android.os.Handler
|
|
16
11
|
import android.os.Looper
|
|
17
12
|
import androidx.core.app.NotificationCompat
|
|
@@ -22,13 +17,10 @@ object NativeCallManager {
|
|
|
22
17
|
const val channelId = "CALL_CHANNEL_V15_URGENT"
|
|
23
18
|
private var currentCallData: Map<String, String>? = null
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
@JvmStatic internal var pendingCallNotification: android.app.Notification? = null
|
|
20
|
+
@JvmStatic internal var pendingCallNotification: Notification? = null
|
|
27
21
|
|
|
28
22
|
@JvmStatic internal var pendingNotificationId: Int? = null
|
|
29
23
|
|
|
30
|
-
fun getCurrentCallData(): Map<String, String>? = currentCallData
|
|
31
|
-
|
|
32
24
|
fun handleIncomingPush(
|
|
33
25
|
context: Context,
|
|
34
26
|
data: Map<String, String>,
|
|
@@ -36,7 +28,6 @@ object NativeCallManager {
|
|
|
36
28
|
Handler(Looper.getMainLooper()).post {
|
|
37
29
|
val uuid = data["callUuid"] ?: return@post
|
|
38
30
|
this.currentCallData = data
|
|
39
|
-
|
|
40
31
|
val name = data["name"] ?: "Incoming Call"
|
|
41
32
|
val notificationId = uuid.hashCode()
|
|
42
33
|
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
@@ -48,15 +39,7 @@ object NativeCallManager {
|
|
|
48
39
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
49
40
|
}
|
|
50
41
|
|
|
51
|
-
//
|
|
52
|
-
val noOpIntent =
|
|
53
|
-
Intent("com.rnsnativecall.ACTION_NOTIFICATION_TAP_NOOP").apply {
|
|
54
|
-
`package` = context.packageName
|
|
55
|
-
putExtra("EXTRA_CALL_UUID", uuid)
|
|
56
|
-
}
|
|
57
|
-
val contentIntent = PendingIntent.getBroadcast(context, notificationId + 3, noOpIntent, pendingFlags)
|
|
58
|
-
|
|
59
|
-
// 2. Full Screen Intent (Wake lockscreen)
|
|
42
|
+
// Intents
|
|
60
43
|
val overlayIntent =
|
|
61
44
|
Intent(context, NotificationOverlayActivity::class.java).apply {
|
|
62
45
|
putExtra("EXTRA_CALL_UUID", uuid)
|
|
@@ -65,7 +48,6 @@ object NativeCallManager {
|
|
|
65
48
|
}
|
|
66
49
|
val fullScreenPendingIntent = PendingIntent.getActivity(context, notificationId, overlayIntent, pendingFlags)
|
|
67
50
|
|
|
68
|
-
// 3. Answer Action
|
|
69
51
|
val answerIntent =
|
|
70
52
|
Intent(context, CallActionReceiver::class.java).apply {
|
|
71
53
|
action = "ACTION_ANSWER"
|
|
@@ -74,7 +56,6 @@ object NativeCallManager {
|
|
|
74
56
|
}
|
|
75
57
|
val answerPendingIntent = PendingIntent.getBroadcast(context, notificationId + 1, answerIntent, pendingFlags)
|
|
76
58
|
|
|
77
|
-
// 4. Reject Action
|
|
78
59
|
val rejectIntent =
|
|
79
60
|
Intent(context, CallActionReceiver::class.java).apply {
|
|
80
61
|
action = "ACTION_REJECT"
|
|
@@ -83,24 +64,25 @@ object NativeCallManager {
|
|
|
83
64
|
}
|
|
84
65
|
val rejectPendingIntent = PendingIntent.getBroadcast(context, notificationId + 2, rejectIntent, pendingFlags)
|
|
85
66
|
|
|
86
|
-
//
|
|
67
|
+
// Channel
|
|
87
68
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
69
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
70
|
+
val channel =
|
|
71
|
+
NotificationChannel(channelId, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
|
|
72
|
+
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
73
|
+
enableVibration(true)
|
|
74
|
+
setBypassDnd(true)
|
|
75
|
+
setSound(
|
|
76
|
+
ringtoneUri,
|
|
77
|
+
AudioAttributes
|
|
78
|
+
.Builder()
|
|
79
|
+
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
|
80
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
81
|
+
.build(),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
notificationManager.createNotificationChannel(channel)
|
|
85
|
+
}
|
|
104
86
|
|
|
105
87
|
val caller =
|
|
106
88
|
Person
|
|
@@ -116,31 +98,19 @@ object NativeCallManager {
|
|
|
116
98
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
117
99
|
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
118
100
|
.setOngoing(true)
|
|
119
|
-
.setAutoCancel(false)
|
|
120
|
-
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
121
|
-
.setWhen(0)
|
|
122
|
-
.setUsesChronometer(false)
|
|
123
|
-
.setContentIntent(contentIntent)
|
|
124
101
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
125
|
-
.setStyle(
|
|
126
|
-
|
|
127
|
-
.forIncomingCall(caller, rejectPendingIntent, answerPendingIntent)
|
|
128
|
-
.setAnswerButtonColorHint(Color.GREEN)
|
|
129
|
-
.setDeclineButtonColorHint(Color.RED),
|
|
130
|
-
).setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
102
|
+
.setStyle(NotificationCompat.CallStyle.forIncomingCall(caller, rejectPendingIntent, answerPendingIntent))
|
|
103
|
+
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
131
104
|
|
|
132
105
|
val notification = builder.build()
|
|
133
106
|
pendingCallNotification = notification
|
|
134
107
|
pendingNotificationId = notificationId
|
|
135
108
|
|
|
136
|
-
//
|
|
109
|
+
// 1. Start the LOUD UI Service (Google allows this for PHONE_CALL)
|
|
137
110
|
val uiIntent = Intent(context, CallUiForegroundService::class.java)
|
|
138
111
|
ContextCompat.startForegroundService(context, uiIntent)
|
|
139
112
|
|
|
140
|
-
//
|
|
141
|
-
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
142
|
-
manager.notify(notificationId, notification)
|
|
143
|
-
|
|
113
|
+
// 2. Stop the SILENT Wake service (which no longer needs DATA_SYNC)
|
|
144
114
|
CallForegroundService.stop(context)
|
|
145
115
|
}
|
|
146
116
|
}
|
|
@@ -151,55 +121,8 @@ object NativeCallManager {
|
|
|
151
121
|
) {
|
|
152
122
|
pendingCallNotification = null
|
|
153
123
|
pendingNotificationId = null
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
157
|
-
|
|
158
|
-
uuid?.let { notificationManager.cancel(it.hashCode()) }
|
|
159
|
-
|
|
124
|
+
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
125
|
+
uuid?.let { manager.cancel(it.hashCode()) }
|
|
160
126
|
context.stopService(Intent(context, CallUiForegroundService::class.java))
|
|
161
127
|
}
|
|
162
|
-
|
|
163
|
-
fun connecting(
|
|
164
|
-
context: Context,
|
|
165
|
-
uuid: String,
|
|
166
|
-
name: String,
|
|
167
|
-
callType: String,
|
|
168
|
-
) {
|
|
169
|
-
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
170
|
-
val builder =
|
|
171
|
-
NotificationCompat
|
|
172
|
-
.Builder(context, channelId)
|
|
173
|
-
.setSmallIcon(context.applicationInfo.icon)
|
|
174
|
-
.setContentTitle(name)
|
|
175
|
-
.setContentText("Connecting…")
|
|
176
|
-
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
177
|
-
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
178
|
-
.setOngoing(true)
|
|
179
|
-
.setStyle(NotificationCompat.BigTextStyle().bigText("Connecting…"))
|
|
180
|
-
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
fun aborting(
|
|
184
|
-
context: Context,
|
|
185
|
-
uuid: String,
|
|
186
|
-
name: String,
|
|
187
|
-
callType: String,
|
|
188
|
-
) {
|
|
189
|
-
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
190
|
-
val builder =
|
|
191
|
-
NotificationCompat
|
|
192
|
-
.Builder(context, channelId)
|
|
193
|
-
.setSmallIcon(context.applicationInfo.icon)
|
|
194
|
-
.setContentTitle(name)
|
|
195
|
-
.setContentText("Aborting…")
|
|
196
|
-
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
197
|
-
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
198
|
-
.setOngoing(true)
|
|
199
|
-
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
fun stopRingtone() {
|
|
203
|
-
// No-op: Sound is now managed by the Notification Channel life-cycle
|
|
204
|
-
}
|
|
205
128
|
}
|
|
@@ -5,18 +5,20 @@ import android.content.Context
|
|
|
5
5
|
import android.content.Intent
|
|
6
6
|
|
|
7
7
|
class UnlockReceiver : BroadcastReceiver() {
|
|
8
|
-
override fun onReceive(
|
|
8
|
+
override fun onReceive(
|
|
9
|
+
context: Context,
|
|
10
|
+
intent: Intent,
|
|
11
|
+
) {
|
|
9
12
|
android.util.Log.d("UnlockReceiver", "Device Unlocked! Action: ${intent.action}")
|
|
10
|
-
|
|
13
|
+
|
|
11
14
|
if (intent.action == Intent.ACTION_USER_PRESENT) {
|
|
12
|
-
val activeData = NativeCallManager.getCurrentCallData()
|
|
13
|
-
|
|
14
|
-
if (activeData != null) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
15
|
+
// val activeData = NativeCallManager.getCurrentCallData()
|
|
16
|
+
|
|
17
|
+
// if (activeData != null) {
|
|
18
|
+
// NativeCallManager.handleIncomingPush(context, activeData)
|
|
19
|
+
// } else {
|
|
20
|
+
// android.util.Log.d("UnlockReceiver", "No active call data found to re-trigger.")
|
|
21
|
+
// }
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
|
-
}
|
|
24
|
+
}
|
package/package.json
CHANGED
package/withNativeCallVoip.js
CHANGED
|
@@ -86,10 +86,9 @@ function withAndroidConfig(config) {
|
|
|
86
86
|
const permissions = [
|
|
87
87
|
'android.permission.VIBRATE',
|
|
88
88
|
'android.permission.FOREGROUND_SERVICE',
|
|
89
|
-
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
|
|
90
|
-
'android.permission.
|
|
91
|
-
'android.permission.
|
|
92
|
-
'android.permission.MANAGE_ONGOING_CALLS', // ADDED THIS
|
|
89
|
+
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
|
|
90
|
+
'android.permission.USE_FULL_SCREEN_INTENT',
|
|
91
|
+
'android.permission.MANAGE_ONGOING_CALLS',
|
|
93
92
|
'android.permission.POST_NOTIFICATIONS',
|
|
94
93
|
'android.permission.WAKE_LOCK',
|
|
95
94
|
'android.permission.DISABLE_KEYGUARD',
|
|
@@ -108,7 +107,7 @@ function withAndroidConfig(config) {
|
|
|
108
107
|
|
|
109
108
|
const services = [
|
|
110
109
|
{ name: 'com.rnsnativecall.CallMessagingService', exported: 'false', filter: 'com.google.firebase.MESSAGING_EVENT' },
|
|
111
|
-
{ name: 'com.rnsnativecall.CallForegroundService'
|
|
110
|
+
{ name: 'com.rnsnativecall.CallForegroundService' },
|
|
112
111
|
{ name: 'com.rnsnativecall.CallUiForegroundService', type: 'phoneCall' },
|
|
113
112
|
{ name: 'com.rnsnativecall.CallHeadlessTask' }
|
|
114
113
|
];
|