@nativetalkcommunications/react-native-call-sdk 0.1.0
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/LICENSE +21 -0
- package/NativetalkCallSdk.podspec +31 -0
- package/README.md +494 -0
- package/android/build.gradle +58 -0
- package/android/gradle.properties +2 -0
- package/android/src/main/AndroidManifest.xml +84 -0
- package/android/src/main/java/io/nativetalk/callsdk/BackgroundService.kt +149 -0
- package/android/src/main/java/io/nativetalk/callsdk/CallActionReceiver.kt +24 -0
- package/android/src/main/java/io/nativetalk/callsdk/CallService.kt +45 -0
- package/android/src/main/java/io/nativetalk/callsdk/Compatibility.kt +96 -0
- package/android/src/main/java/io/nativetalk/callsdk/CoreManager.kt +801 -0
- package/android/src/main/java/io/nativetalk/callsdk/NativetalkCallScreeningService.kt +105 -0
- package/android/src/main/java/io/nativetalk/callsdk/NativetalkCallSdkModule.kt +205 -0
- package/android/src/main/java/io/nativetalk/callsdk/NativetalkCallSdkPackage.kt +18 -0
- package/android/src/main/java/io/nativetalk/callsdk/TelephonyMonitor.kt +229 -0
- package/android/src/main/java/io/nativetalk/callsdk/Utils.kt +42 -0
- package/android/src/main/res/drawable/ic_nativetalk_call.xml +9 -0
- package/android/src/main/res/values/strings.xml +9 -0
- package/app.plugin.js +1 -0
- package/ios/NativetalkCallSdk-Bridging-Header.h +4 -0
- package/ios/NativetalkCallSdk.swift +738 -0
- package/ios/NativetalkCallSdkBridge.m +35 -0
- package/lib/commonjs/CallProvider.js +602 -0
- package/lib/commonjs/helpers.js +173 -0
- package/lib/commonjs/index.js +96 -0
- package/lib/commonjs/native.js +146 -0
- package/lib/commonjs/types.js +8 -0
- package/lib/commonjs/ui/Avatar.js +29 -0
- package/lib/commonjs/ui/Dialer.js +189 -0
- package/lib/commonjs/ui/IncomingCallView.js +128 -0
- package/lib/commonjs/ui/OutgoingCallView.js +117 -0
- package/lib/commonjs/ui/index.js +22 -0
- package/lib/commonjs/ui/theme.js +21 -0
- package/lib/module/CallProvider.js +573 -0
- package/lib/module/helpers.js +161 -0
- package/lib/module/index.js +57 -0
- package/lib/module/native.js +123 -0
- package/lib/module/types.js +7 -0
- package/lib/module/ui/Avatar.js +22 -0
- package/lib/module/ui/Dialer.js +162 -0
- package/lib/module/ui/IncomingCallView.js +101 -0
- package/lib/module/ui/OutgoingCallView.js +110 -0
- package/lib/module/ui/index.js +13 -0
- package/lib/module/ui/theme.js +17 -0
- package/lib/typescript/CallProvider.d.ts +46 -0
- package/lib/typescript/helpers.d.ts +52 -0
- package/lib/typescript/index.d.ts +77 -0
- package/lib/typescript/native.d.ts +53 -0
- package/lib/typescript/types.d.ts +155 -0
- package/lib/typescript/ui/Avatar.d.ts +13 -0
- package/lib/typescript/ui/Dialer.d.ts +29 -0
- package/lib/typescript/ui/IncomingCallView.d.ts +39 -0
- package/lib/typescript/ui/OutgoingCallView.d.ts +28 -0
- package/lib/typescript/ui/index.d.ts +13 -0
- package/lib/typescript/ui/theme.d.ts +20 -0
- package/linphonesw-pod/Sources/LinphoneSdkInfos.swift +4 -0
- package/linphonesw-pod/Sources/LinphoneWrapper.swift +42949 -0
- package/linphonesw-pod/linphonesw.podspec +46 -0
- package/package.json +90 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.js +78 -0
- package/plugin/build/withIos.js +66 -0
- package/src/CallProvider.tsx +675 -0
- package/src/helpers.ts +179 -0
- package/src/index.ts +84 -0
- package/src/native.ts +185 -0
- package/src/types.ts +202 -0
- package/src/ui/Avatar.tsx +46 -0
- package/src/ui/Dialer.tsx +248 -0
- package/src/ui/IncomingCallView.tsx +161 -0
- package/src/ui/OutgoingCallView.tsx +203 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/theme.ts +36 -0
- package/ui/package.json +6 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
package io.nativetalk.callsdk
|
|
2
|
+
|
|
3
|
+
import android.app.AlarmManager
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.app.PendingIntent
|
|
7
|
+
import android.app.Service
|
|
8
|
+
import android.content.Context
|
|
9
|
+
import android.content.Intent
|
|
10
|
+
import android.net.ConnectivityManager
|
|
11
|
+
import android.net.Network
|
|
12
|
+
import android.net.NetworkCapabilities
|
|
13
|
+
import android.net.NetworkRequest
|
|
14
|
+
import android.os.IBinder
|
|
15
|
+
import android.os.PowerManager
|
|
16
|
+
import android.os.SystemClock
|
|
17
|
+
import android.util.Log
|
|
18
|
+
import androidx.core.app.NotificationCompat
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Long-running foreground service that keeps the SIP registration warm even
|
|
22
|
+
* when the user has backgrounded the app. Hosts a wake lock and (re)starts
|
|
23
|
+
* [CallService] if the OS kills it.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle: `startService()` → `onCreate` (wake lock + TelephonyMonitor) →
|
|
26
|
+
* `onStartCommand` (foreground notification + spawn CallService). On
|
|
27
|
+
* task-removal we schedule an alarm to relaunch ourselves a second later.
|
|
28
|
+
*/
|
|
29
|
+
class BackgroundService : Service() {
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
private const val NOTIFICATION_ID = 1000
|
|
33
|
+
private const val CHANNEL_ID = "nativetalk_background"
|
|
34
|
+
private const val TAG = "NativetalkCallSdk.BgService"
|
|
35
|
+
|
|
36
|
+
// Set to `false` by the JS bridge during explicit logout so that
|
|
37
|
+
// onTaskRemoved doesn't immediately respawn us. @Volatile because
|
|
38
|
+
// the flag is read/written from different threads (RN bridge thread
|
|
39
|
+
// vs main thread handling task-removal).
|
|
40
|
+
@Volatile var shouldRestart = true
|
|
41
|
+
|
|
42
|
+
fun startService(context: Context) {
|
|
43
|
+
val intent = Intent(context, BackgroundService::class.java)
|
|
44
|
+
// `startForegroundService` (not `startService`) is required on
|
|
45
|
+
// Android 8+ — the OS gives us a 5-second window to call
|
|
46
|
+
// `startForeground` or it kills us with a SecurityException.
|
|
47
|
+
context.startForegroundService(intent)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fun stopService(context: Context) {
|
|
51
|
+
val intent = Intent(context, BackgroundService::class.java)
|
|
52
|
+
context.stopService(intent)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private var wakeLock: PowerManager.WakeLock? = null
|
|
57
|
+
private var connectivityManager: ConnectivityManager? = null
|
|
58
|
+
|
|
59
|
+
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
|
60
|
+
override fun onAvailable(network: Network) {
|
|
61
|
+
Log.d(TAG, "Network available — triggering re-registration")
|
|
62
|
+
CoreManager.refreshRegisters()
|
|
63
|
+
}
|
|
64
|
+
override fun onLost(network: Network) {
|
|
65
|
+
Log.d(TAG, "Network lost")
|
|
66
|
+
CoreManager.setNetworkReachable(false)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun onCreate() {
|
|
71
|
+
super.onCreate()
|
|
72
|
+
createChannel()
|
|
73
|
+
val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
74
|
+
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NativetalkCallSdk::BackgroundService")
|
|
75
|
+
// 10-minute timeout: Android's StrictMode warns if a wake lock has
|
|
76
|
+
// no timeout, and 10min is long enough to register & answer a call
|
|
77
|
+
// but short enough that a buggy service can't drain the battery.
|
|
78
|
+
wakeLock?.acquire(10 * 60 * 1000L)
|
|
79
|
+
|
|
80
|
+
TelephonyMonitor.start(applicationContext)
|
|
81
|
+
|
|
82
|
+
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
83
|
+
val request = NetworkRequest.Builder()
|
|
84
|
+
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
85
|
+
.build()
|
|
86
|
+
connectivityManager?.registerNetworkCallback(request, networkCallback)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
90
|
+
// Must call startForeground within ~5s of startForegroundService —
|
|
91
|
+
// do it first thing here.
|
|
92
|
+
startForeground(NOTIFICATION_ID, buildNotification())
|
|
93
|
+
// CallService owns the per-call notification; this service owns
|
|
94
|
+
// the "always-on" registration notification.
|
|
95
|
+
applicationContext.startService(Intent(applicationContext, CallService::class.java))
|
|
96
|
+
// START_STICKY: if the OS kills us under memory pressure, restart
|
|
97
|
+
// us with a null intent. That's what we want for a keep-alive.
|
|
98
|
+
return START_STICKY
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun onBind(intent: Intent?): IBinder? = null
|
|
102
|
+
|
|
103
|
+
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
104
|
+
// Fires when the user swipes the app from recents. The OS often
|
|
105
|
+
// kills our service shortly after, so we set a 1-second alarm to
|
|
106
|
+
// relaunch ourselves. The alarm survives even if our process dies.
|
|
107
|
+
if (shouldRestart) {
|
|
108
|
+
val restart = Intent(applicationContext, BackgroundService::class.java)
|
|
109
|
+
val pending = PendingIntent.getService(
|
|
110
|
+
this,
|
|
111
|
+
1,
|
|
112
|
+
restart,
|
|
113
|
+
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
|
|
114
|
+
)
|
|
115
|
+
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
|
116
|
+
alarm.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, pending)
|
|
117
|
+
}
|
|
118
|
+
super.onTaskRemoved(rootIntent)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun onDestroy() {
|
|
122
|
+
super.onDestroy()
|
|
123
|
+
runCatching { connectivityManager?.unregisterNetworkCallback(networkCallback) }
|
|
124
|
+
wakeLock?.release()
|
|
125
|
+
TelephonyMonitor.stop()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun createChannel() {
|
|
129
|
+
val ch = NotificationChannel(
|
|
130
|
+
CHANNEL_ID,
|
|
131
|
+
getString(R.string.nativetalk_call_sdk_notif_channel_background_name),
|
|
132
|
+
NotificationManager.IMPORTANCE_LOW
|
|
133
|
+
).apply {
|
|
134
|
+
description = getString(R.string.nativetalk_call_sdk_ready_body)
|
|
135
|
+
setShowBadge(false)
|
|
136
|
+
}
|
|
137
|
+
val manager = getSystemService(NotificationManager::class.java)
|
|
138
|
+
manager.createNotificationChannel(ch)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private fun buildNotification() = NotificationCompat.Builder(this, CHANNEL_ID)
|
|
142
|
+
.setContentTitle(getString(R.string.nativetalk_call_sdk_ready_title))
|
|
143
|
+
.setContentText(getString(R.string.nativetalk_call_sdk_ready_body))
|
|
144
|
+
.setSmallIcon(R.drawable.ic_nativetalk_call)
|
|
145
|
+
.setOngoing(true)
|
|
146
|
+
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
147
|
+
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
148
|
+
.build()
|
|
149
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package io.nativetalk.callsdk
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.util.Log
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Receives Answer / Decline button taps on the heads-up call notification and
|
|
10
|
+
* forwards them to [CoreManager].
|
|
11
|
+
*/
|
|
12
|
+
class CallActionReceiver : BroadcastReceiver() {
|
|
13
|
+
override fun onReceive(context: Context?, intent: Intent?) {
|
|
14
|
+
Log.d(TAG, "Received action: ${intent?.action}")
|
|
15
|
+
when (intent?.action) {
|
|
16
|
+
CoreManager.ACTION_ANSWER_CALL -> CoreManager.answer()
|
|
17
|
+
CoreManager.ACTION_DECLINE_CALL -> CoreManager.decline()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private companion object {
|
|
22
|
+
const val TAG = "NativetalkCallSdk.Receiver"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package io.nativetalk.callsdk
|
|
2
|
+
|
|
3
|
+
import android.app.Service
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.os.IBinder
|
|
6
|
+
import android.util.Log
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Foreground service that owns the call notification.
|
|
10
|
+
*
|
|
11
|
+
* Started by [BackgroundService] and by [NativetalkCallSdkModule.startNativeServices].
|
|
12
|
+
* The actual lifecycle of the Linphone core lives in [CoreManager]; this
|
|
13
|
+
* service is mostly a holder for the foreground-notification slot.
|
|
14
|
+
*/
|
|
15
|
+
class CallService : Service() {
|
|
16
|
+
|
|
17
|
+
override fun onCreate() {
|
|
18
|
+
super.onCreate()
|
|
19
|
+
Log.i(TAG, "onCreate")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
23
|
+
Log.i(TAG, "onStartCommand")
|
|
24
|
+
CoreManager.onCallServiceStarted(this)
|
|
25
|
+
return super.onStartCommand(intent, flags, startId)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun onBind(intent: Intent?): IBinder? = null
|
|
29
|
+
|
|
30
|
+
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
31
|
+
if (BackgroundService.shouldRestart) {
|
|
32
|
+
startService(Intent(applicationContext, CallService::class.java))
|
|
33
|
+
}
|
|
34
|
+
super.onTaskRemoved(rootIntent)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override fun onDestroy() {
|
|
38
|
+
super.onDestroy()
|
|
39
|
+
Log.i(TAG, "onDestroy")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private companion object {
|
|
43
|
+
const val TAG = "NativetalkCallSdk.CallService"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package io.nativetalk.callsdk
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.annotation.SuppressLint
|
|
5
|
+
import android.app.Notification
|
|
6
|
+
import android.app.Service
|
|
7
|
+
import android.content.Context
|
|
8
|
+
import android.content.pm.PackageManager
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import androidx.annotation.RequiresApi
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Thin shims over `Service.startForeground(…)` and the runtime
|
|
15
|
+
* POST_NOTIFICATIONS check. Required because Android 14 (API 34) made the
|
|
16
|
+
* foreground-service-type parameter MANDATORY for `phoneCall` / `microphone`
|
|
17
|
+
* services — calling the 2-arg overload on API 34 throws SecurityException,
|
|
18
|
+
* while the 3-arg overload doesn't exist on older versions.
|
|
19
|
+
*
|
|
20
|
+
* Centralising the version dispatch here keeps [CoreManager] free of
|
|
21
|
+
* `Build.VERSION.SDK_INT` checks.
|
|
22
|
+
*/
|
|
23
|
+
@SuppressLint("NewApi")
|
|
24
|
+
class Compatibility {
|
|
25
|
+
companion object {
|
|
26
|
+
private const val TAG = "NativetalkCallSdk.Compat"
|
|
27
|
+
|
|
28
|
+
// Type bitmask values, copied here so callers don't need to depend
|
|
29
|
+
// on android.content.pm.ServiceInfo (which would force API-30+).
|
|
30
|
+
// 4 = ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
|
|
31
|
+
// 128 = ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
|
32
|
+
// OR them together when a call needs both mic + phoneCall (active call).
|
|
33
|
+
const val FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4
|
|
34
|
+
const val FOREGROUND_SERVICE_TYPE_MICROPHONE = 128
|
|
35
|
+
|
|
36
|
+
// Dispatch to the right startForeground overload by API level.
|
|
37
|
+
// `UPSIDE_DOWN_CAKE` is the Build.VERSION_CODES constant for API 34.
|
|
38
|
+
fun startServiceForeground(
|
|
39
|
+
service: Service,
|
|
40
|
+
id: Int,
|
|
41
|
+
notification: Notification,
|
|
42
|
+
foregroundServiceType: Int
|
|
43
|
+
) {
|
|
44
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
45
|
+
Api34.startServiceForeground(service, id, notification, foregroundServiceType)
|
|
46
|
+
} else {
|
|
47
|
+
Legacy.startServiceForeground(service, id, notification)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// POST_NOTIFICATIONS only exists as a runtime permission on Android
|
|
52
|
+
// 13+ (TIRAMISU). On older versions, having the manifest entry is
|
|
53
|
+
// enough — return true so callers don't need their own version check.
|
|
54
|
+
fun isPostNotificationsPermissionGranted(context: Context): Boolean {
|
|
55
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
56
|
+
return context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
|
57
|
+
}
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// API 34+ path. The `@RequiresApi` annotation lets the IDE catch any caller
|
|
64
|
+
// that bypasses [Compatibility.startServiceForeground] and reaches this
|
|
65
|
+
// directly on an older device.
|
|
66
|
+
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
|
67
|
+
private object Api34 {
|
|
68
|
+
fun startServiceForeground(
|
|
69
|
+
service: Service,
|
|
70
|
+
id: Int,
|
|
71
|
+
notification: Notification,
|
|
72
|
+
foregroundServiceType: Int
|
|
73
|
+
) {
|
|
74
|
+
// Wrapped in try/catch because even with the right type mask the
|
|
75
|
+
// OS can still reject the foreground transition (e.g. user disabled
|
|
76
|
+
// POST_NOTIFICATIONS, or background-start restrictions). We log
|
|
77
|
+
// rather than crash because the call itself usually still works —
|
|
78
|
+
// just without a notification.
|
|
79
|
+
try {
|
|
80
|
+
service.startForeground(id, notification, foregroundServiceType)
|
|
81
|
+
} catch (e: Exception) {
|
|
82
|
+
Log.e("NativetalkCallSdk.Compat", "startForeground (API34) failed", e)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Pre-API-34 path: the 2-arg overload. No type mask needed.
|
|
88
|
+
private object Legacy {
|
|
89
|
+
fun startServiceForeground(service: Service, id: Int, notification: Notification) {
|
|
90
|
+
try {
|
|
91
|
+
service.startForeground(id, notification)
|
|
92
|
+
} catch (e: Exception) {
|
|
93
|
+
Log.e("NativetalkCallSdk.Compat", "startForeground (legacy) failed", e)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|