@poolse/react-native-calls 0.1.1 → 0.1.3

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.
@@ -57,14 +57,28 @@ internal class AndroidRingSurface(
57
57
  )
58
58
  }
59
59
 
60
- val fullScreen = PendingIntent.getActivity(
61
- context,
62
- ring.callId.hashCode(),
63
- Intent(context, IncomingCallActivity::class.java)
60
+ fun incomingActivity(autoAnswer: Boolean, requestOffset: Int): PendingIntent {
61
+ val intent = Intent(context, IncomingCallActivity::class.java)
64
62
  .putExtra(EXTRA_RING, extras)
65
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP),
66
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
67
- )
63
+ .putExtra(EXTRA_AUTO_ANSWER, autoAnswer)
64
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
65
+ return PendingIntent.getActivity(
66
+ context,
67
+ ring.callId.hashCode() + requestOffset,
68
+ intent,
69
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
70
+ )
71
+ }
72
+
73
+ val fullScreen = incomingActivity(autoAnswer = false, requestOffset = 0)
74
+
75
+ // Answer launches an ACTIVITY, not a broadcast: a background receiver
76
+ // cannot start an activity (Android 10+), so answering from the
77
+ // notification used to connect audio while the app stayed hidden. An
78
+ // activity launched from a notification tap is allowed to come
79
+ // forward — it answers and brings the app up. Decline stays a
80
+ // broadcast; it needs no UI.
81
+ val answer = incomingActivity(autoAnswer = true, requestOffset = 2)
68
82
 
69
83
  val caller = Person.Builder()
70
84
  .setName(ring.conversationName ?: ring.callerName ?: "Incoming call")
@@ -83,7 +97,7 @@ internal class AndroidRingSurface(
83
97
  NotificationCompat.CallStyle.forIncomingCall(
84
98
  caller,
85
99
  actionPending(ACTION_DECLINE, 1),
86
- actionPending(ACTION_ANSWER, 2),
100
+ answer,
87
101
  ),
88
102
  )
89
103
  .build()
@@ -169,6 +183,7 @@ internal class AndroidRingSurface(
169
183
  const val ACTION_DECLINE = "DECLINE"
170
184
  const val ACTION_OPEN = "OPEN"
171
185
  const val EXTRA_RING = "poolse_ring"
186
+ const val EXTRA_AUTO_ANSWER = "poolse_auto_answer"
172
187
 
173
188
  /** Stable per-call notification id, so teardown targets the right one. */
174
189
  fun String.notificationId(): Int = hashCode()
@@ -25,6 +25,23 @@ class CallForegroundService : Service() {
25
25
 
26
26
  override fun onBind(intent: Intent?): IBinder? = null
27
27
 
28
+ // The ongoing-call notification has its own channel, created here so it
29
+ // exists on an OUTGOING call too — the incoming ring surface's channel
30
+ // is only made when a ring shows, so a placed call would otherwise post
31
+ // to a missing channel and startForeground would crash.
32
+ private fun ensureChannel() {
33
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
34
+ val manager = getSystemService(android.app.NotificationManager::class.java)
35
+ if (manager.getNotificationChannel(CHANNEL_ID) != null) return
36
+ manager.createNotificationChannel(
37
+ android.app.NotificationChannel(
38
+ CHANNEL_ID,
39
+ "Ongoing calls",
40
+ android.app.NotificationManager.IMPORTANCE_LOW,
41
+ ).apply { setSound(null, null) },
42
+ )
43
+ }
44
+
28
45
  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
29
46
  val media = intent?.getStringExtra(EXTRA_MEDIA) ?: "audio"
30
47
  startInForeground(media)
@@ -34,8 +51,9 @@ class CallForegroundService : Service() {
34
51
  }
35
52
 
36
53
  private fun startInForeground(media: String) {
54
+ ensureChannel()
37
55
  val config = CallCore.peekConfig()
38
- val notification: Notification = NotificationCompat.Builder(this, AndroidRingSurface.CHANNEL_ID)
56
+ val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
39
57
  .setSmallIcon(config?.smallIcon ?: android.R.drawable.sym_call_outgoing)
40
58
  .setContentTitle("Ongoing call")
41
59
  .setContentText(if (media == "video") "Video call" else "Voice call")
@@ -58,6 +76,7 @@ class CallForegroundService : Service() {
58
76
  companion object : CallSessionPort {
59
77
  private const val NOTIFICATION_ID = 0x9C0FE
60
78
  private const val EXTRA_MEDIA = "media"
79
+ private const val CHANNEL_ID = "poolse_ongoing_call"
61
80
 
62
81
  private lateinit var appContext: Context
63
82
 
@@ -37,6 +37,16 @@ class IncomingCallActivity : Activity() {
37
37
  val ring = intent.getBundleExtra(EXTRA_RING)?.toRingContext()
38
38
  if (ring == null) { finish(); return }
39
39
 
40
+ // Answered from the notification: this activity exists only to be the
41
+ // foreground context that is ALLOWED to bring the app up (a
42
+ // background receiver is not). Answer and get out before drawing —
43
+ // onUserAnswer launches the app over the keyguard.
44
+ if (intent.getBooleanExtra(AndroidRingSurface.EXTRA_AUTO_ANSWER, false)) {
45
+ CallCore.get(this).onUserAnswer(ring)
46
+ finish()
47
+ return
48
+ }
49
+
40
50
  val title = ring.conversationName ?: ring.callerName ?: "Incoming call"
41
51
  val body = if (ring.media == "video") "Incoming video call" else "Incoming call"
42
52
  setContentView(buildScene(title, body, ring))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolse/react-native-calls",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Native call core for poolse — Kotlin/Swift calling engine with a thin React Native bridge. Ring, answer and carry audio with the app killed and the phone locked.",
5
5
  "license": "MIT",
6
6
  "type": "module",