@stream-io/react-native-callingx 0.1.0-beta.7 → 0.1.1-beta.1
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 +7 -1
- package/android/src/main/AndroidManifest.xml +31 -1
- package/android/src/main/java/io/getstream/rn/callingx/CallEventBroadcastReceiver.kt +17 -0
- package/android/src/main/java/io/getstream/rn/callingx/CallRegistrationStore.kt +145 -0
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +301 -83
- package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +148 -390
- package/android/src/main/java/io/getstream/rn/callingx/StreamMessagingService.kt +48 -0
- package/android/src/main/java/io/getstream/rn/callingx/model/Call.kt +1 -0
- package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +188 -48
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +14 -8
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +12 -1
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +7 -0
- package/android/src/main/java/io/getstream/rn/callingx/repo/CallRepository.kt +38 -19
- package/android/src/main/java/io/getstream/rn/callingx/repo/LegacyCallRepository.kt +64 -55
- package/android/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +241 -195
- package/android/src/main/java/io/getstream/rn/callingx/utils/CallEventBus.kt +61 -0
- package/android/src/main/java/io/getstream/rn/callingx/utils/SettingsStore.kt +51 -0
- package/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt +12 -3
- package/android/src/oldarch/java/io/getstream/rn/callingx/CallingxModule.kt +13 -3
- package/dist/module/CallingxModule.js +20 -24
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/spec/NativeCallingx.js.map +1 -1
- package/dist/module/utils/constants.js +24 -14
- package/dist/module/utils/constants.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +4 -2
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts +7 -4
- package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +33 -5
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/constants.d.ts +2 -3
- package/dist/typescript/src/utils/constants.d.ts.map +1 -1
- package/ios/AudioSessionManager.swift +2 -2
- package/ios/Callingx.mm +41 -17
- package/ios/CallingxImpl.swift +213 -83
- package/ios/Settings.swift +2 -2
- package/ios/UUIDStorage.swift +10 -10
- package/ios/VoipNotificationsManager.swift +8 -8
- package/package.json +4 -2
- package/src/CallingxModule.ts +20 -21
- package/src/spec/NativeCallingx.ts +10 -6
- package/src/types.ts +36 -4
- package/src/utils/constants.ts +23 -12
- /package/android/src/main/java/io/getstream/rn/callingx/{ResourceUtils.kt → utils/ResourceUtils.kt} +0 -0
- /package/android/src/main/java/io/getstream/rn/callingx/{Utils.kt → utils/Utils.kt} +0 -0
|
@@ -2,7 +2,10 @@ package io.getstream.rn.callingx
|
|
|
2
2
|
|
|
3
3
|
import android.app.Notification
|
|
4
4
|
import android.app.Service
|
|
5
|
+
import android.content.BroadcastReceiver
|
|
6
|
+
import android.content.Context
|
|
5
7
|
import android.content.Intent
|
|
8
|
+
import android.content.IntentFilter
|
|
6
9
|
import android.content.pm.ServiceInfo
|
|
7
10
|
import android.net.Uri
|
|
8
11
|
import android.os.Binder
|
|
@@ -11,11 +14,16 @@ import android.os.Bundle
|
|
|
11
14
|
import android.os.IBinder
|
|
12
15
|
import android.telecom.DisconnectCause
|
|
13
16
|
import android.util.Log
|
|
17
|
+
import androidx.core.content.ContextCompat
|
|
18
|
+
import androidx.core.net.toUri
|
|
14
19
|
import io.getstream.rn.callingx.model.Call
|
|
15
20
|
import io.getstream.rn.callingx.model.CallAction
|
|
16
21
|
import io.getstream.rn.callingx.notifications.CallNotificationManager
|
|
22
|
+
import io.getstream.rn.callingx.notifications.NotificationChannelsManager
|
|
23
|
+
import io.getstream.rn.callingx.notifications.NotificationsConfig
|
|
17
24
|
import io.getstream.rn.callingx.repo.CallRepository
|
|
18
25
|
import io.getstream.rn.callingx.repo.CallRepositoryFactory
|
|
26
|
+
import io.getstream.rn.callingx.utils.SettingsStore
|
|
19
27
|
import kotlinx.coroutines.CoroutineScope
|
|
20
28
|
import kotlinx.coroutines.SupervisorJob
|
|
21
29
|
import kotlinx.coroutines.cancel
|
|
@@ -45,8 +53,8 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
45
53
|
internal const val EXTRA_URI = "extra_uri"
|
|
46
54
|
internal const val EXTRA_IS_VIDEO = "extra_is_video"
|
|
47
55
|
internal const val EXTRA_DISPLAY_TITLE = "displayTitle"
|
|
48
|
-
internal const val EXTRA_DISPLAY_SUBTITLE = "displaySubtitle"
|
|
49
56
|
internal const val EXTRA_DISPLAY_OPTIONS = "display_options"
|
|
57
|
+
internal const val EXTRA_ACTION = "action_name"
|
|
50
58
|
// Background task extras
|
|
51
59
|
internal const val EXTRA_TASK_NAME = "task_name"
|
|
52
60
|
internal const val EXTRA_TASK_DATA = "task_data"
|
|
@@ -58,7 +66,61 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
58
66
|
internal const val ACTION_START_BACKGROUND_TASK = "start_background_task"
|
|
59
67
|
internal const val ACTION_STOP_BACKGROUND_TASK = "stop_background_task"
|
|
60
68
|
internal const val ACTION_STOP_SERVICE = "stop_service"
|
|
69
|
+
internal const val ACTION_PROCESS_ACTION = "execute_action"
|
|
61
70
|
internal const val ACTION_REGISTRATION_FAILED = "registration_failed"
|
|
71
|
+
|
|
72
|
+
fun startIncomingCallFromPush(context: Context, data: Map<String, String>) {
|
|
73
|
+
debugLog(TAG, "[service] startIncomingCallFromPush: Starting incoming call from push")
|
|
74
|
+
|
|
75
|
+
// Check if we are allowed to post call notifications (moved from JS layer).
|
|
76
|
+
val notificationsConfig = NotificationsConfig.loadNotificationsConfig(context)
|
|
77
|
+
val notificationChannelsManager =
|
|
78
|
+
NotificationChannelsManager(context).apply {
|
|
79
|
+
setNotificationsConfig(notificationsConfig)
|
|
80
|
+
}
|
|
81
|
+
val notificationStatus = notificationChannelsManager.getNotificationStatus()
|
|
82
|
+
if (!notificationStatus.canPost) {
|
|
83
|
+
debugLog(
|
|
84
|
+
TAG,
|
|
85
|
+
"[service] startIncomingCallFromPush: Cannot post notifications, skipping incoming call"
|
|
86
|
+
)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
val shouldRejectCallWhenBusy = SettingsStore.shouldRejectCallWhenBusy(context)
|
|
91
|
+
if (shouldRejectCallWhenBusy && CallRegistrationStore.hasRegisteredCall()) {
|
|
92
|
+
debugLog(
|
|
93
|
+
TAG,
|
|
94
|
+
"[service] startIncomingCallFromPush: Registered call found and rejectCallWhenBusy is enabled, skipping incoming call"
|
|
95
|
+
)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
val callCid = data["call_cid"]
|
|
100
|
+
if (callCid.isNullOrEmpty()) {
|
|
101
|
+
debugLog(
|
|
102
|
+
TAG,
|
|
103
|
+
"[service] startIncomingCallFromPush: Call CID is null or empty, skipping"
|
|
104
|
+
)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
val callName = data["created_by_display_name"].orEmpty()
|
|
109
|
+
val isVideo = data["video"] == "true"
|
|
110
|
+
|
|
111
|
+
CallRegistrationStore.trackCallRegistration(callCid, null)
|
|
112
|
+
|
|
113
|
+
val intent =
|
|
114
|
+
Intent(context, CallService::class.java).apply {
|
|
115
|
+
action = ACTION_INCOMING_CALL
|
|
116
|
+
putExtra(EXTRA_CALL_ID, callCid)
|
|
117
|
+
putExtra(EXTRA_URI, callCid.toUri())
|
|
118
|
+
putExtra(EXTRA_NAME, callName)
|
|
119
|
+
putExtra(EXTRA_IS_VIDEO, isVideo)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ContextCompat.startForegroundService(context, intent)
|
|
123
|
+
}
|
|
62
124
|
}
|
|
63
125
|
|
|
64
126
|
inner class CallServiceBinder : Binder() {
|
|
@@ -71,9 +133,72 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
71
133
|
|
|
72
134
|
private val binder = CallServiceBinder()
|
|
73
135
|
private val scope: CoroutineScope = CoroutineScope(SupervisorJob())
|
|
136
|
+
private val actionProcessingLock = Object()
|
|
74
137
|
|
|
75
138
|
private var isInForeground = false
|
|
76
139
|
|
|
140
|
+
private val optimisticNotificationReceiver =
|
|
141
|
+
object : BroadcastReceiver() {
|
|
142
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
143
|
+
val callId = intent.getStringExtra(CallingxModuleImpl.EXTRA_CALL_ID) ?: return
|
|
144
|
+
when (intent.action) {
|
|
145
|
+
CallingxModuleImpl.CALL_OPTIMISTIC_ACCEPT_ACTION -> {
|
|
146
|
+
debugLog(
|
|
147
|
+
TAG,
|
|
148
|
+
"[service] optimisticReceiver: Optimistic accept for $callId"
|
|
149
|
+
)
|
|
150
|
+
notificationManager.stopRingtone()
|
|
151
|
+
notificationManager.setOptimisticState(
|
|
152
|
+
callId,
|
|
153
|
+
CallNotificationManager.OptimisticState.ACCEPTING
|
|
154
|
+
)
|
|
155
|
+
val call = callRepository.getCall(callId)
|
|
156
|
+
if (call != null) {
|
|
157
|
+
notificationManager.updateCallNotification(callId, call)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
CallingxModuleImpl.CALL_END_ACTION -> {
|
|
161
|
+
val source = intent.getStringExtra(CallingxModuleImpl.EXTRA_SOURCE)
|
|
162
|
+
val cause =
|
|
163
|
+
intent.getStringExtra(CallingxModuleImpl.EXTRA_DISCONNECT_CAUSE)
|
|
164
|
+
val rejectedCause =
|
|
165
|
+
getDisconnectCauseString(
|
|
166
|
+
DisconnectCause(DisconnectCause.REJECTED)
|
|
167
|
+
)
|
|
168
|
+
val call = callRepository.getCall(callId)
|
|
169
|
+
|
|
170
|
+
val isSysSource =
|
|
171
|
+
source == CallRepository.EventSource.SYS.name.lowercase()
|
|
172
|
+
|
|
173
|
+
// we handle optimistic updates only if incoming call (non-answered) was rejected within notification action
|
|
174
|
+
if (!isSysSource ||
|
|
175
|
+
cause != rejectedCause ||
|
|
176
|
+
call == null ||
|
|
177
|
+
!call.isIncoming() ||
|
|
178
|
+
call.isActive
|
|
179
|
+
) {
|
|
180
|
+
debugLog(
|
|
181
|
+
TAG,
|
|
182
|
+
"[service] optimisticReceiver: Skipping optimistic reject for $callId"
|
|
183
|
+
)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
debugLog(
|
|
188
|
+
TAG,
|
|
189
|
+
"[service] optimisticReceiver: Optimistic reject for $callId"
|
|
190
|
+
)
|
|
191
|
+
notificationManager.stopRingtone()
|
|
192
|
+
notificationManager.setOptimisticState(
|
|
193
|
+
callId,
|
|
194
|
+
CallNotificationManager.OptimisticState.REJECTING
|
|
195
|
+
)
|
|
196
|
+
notificationManager.updateCallNotification(callId, call)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
77
202
|
override fun onCreate() {
|
|
78
203
|
super.onCreate()
|
|
79
204
|
debugLog(TAG, "[service] onCreate: TelecomCallService created")
|
|
@@ -83,14 +208,26 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
83
208
|
callRepository = CallRepositoryFactory.create(applicationContext)
|
|
84
209
|
callRepository.setListener(this)
|
|
85
210
|
|
|
86
|
-
|
|
211
|
+
val filter =
|
|
212
|
+
IntentFilter().apply {
|
|
213
|
+
addAction(CallingxModuleImpl.CALL_OPTIMISTIC_ACCEPT_ACTION)
|
|
214
|
+
addAction(CallingxModuleImpl.CALL_END_ACTION)
|
|
215
|
+
}
|
|
216
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
217
|
+
registerReceiver(optimisticNotificationReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
|
218
|
+
} else {
|
|
219
|
+
@Suppress("UnspecifiedRegisterReceiverFlag")
|
|
220
|
+
registerReceiver(optimisticNotificationReceiver, filter)
|
|
221
|
+
}
|
|
87
222
|
}
|
|
88
223
|
|
|
89
224
|
override fun onDestroy() {
|
|
90
225
|
super.onDestroy()
|
|
91
226
|
debugLog(TAG, "[service] onDestroy: TelecomCallService destroyed")
|
|
92
227
|
|
|
93
|
-
|
|
228
|
+
unregisterReceiver(optimisticNotificationReceiver)
|
|
229
|
+
|
|
230
|
+
notificationManager.cancelAllNotifications()
|
|
94
231
|
notificationManager.stopRingtone()
|
|
95
232
|
callRepository.release()
|
|
96
233
|
headlessJSManager.release()
|
|
@@ -124,23 +261,28 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
124
261
|
registerCall(intent, false)
|
|
125
262
|
}
|
|
126
263
|
ACTION_START_BACKGROUND_TASK -> {
|
|
127
|
-
if (!isInForeground) {
|
|
128
|
-
debugLog(TAG, "[service] onStartCommand: Starting foreground for background task")
|
|
129
|
-
// for now bg task is intended to be used after a call registered and
|
|
130
|
-
// notification is shown, so we don't need to show a separate notification for
|
|
131
|
-
// bg task
|
|
132
|
-
// startForeground(CallNotificationManager.NOTIFICATION_ID, notification)
|
|
133
|
-
// isInForeground = true
|
|
134
|
-
}
|
|
135
|
-
|
|
136
264
|
startBackgroundTask(intent)
|
|
265
|
+
return START_NOT_STICKY
|
|
137
266
|
}
|
|
138
267
|
ACTION_STOP_BACKGROUND_TASK -> {
|
|
139
268
|
stopBackgroundTask()
|
|
269
|
+
return START_NOT_STICKY
|
|
140
270
|
}
|
|
141
271
|
ACTION_UPDATE_CALL -> {
|
|
142
272
|
updateCall(intent)
|
|
143
273
|
}
|
|
274
|
+
ACTION_PROCESS_ACTION -> {
|
|
275
|
+
processAction(intent)
|
|
276
|
+
}
|
|
277
|
+
ACTION_STOP_SERVICE -> {
|
|
278
|
+
if (isInForeground) {
|
|
279
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
280
|
+
isInForeground = false
|
|
281
|
+
}
|
|
282
|
+
notificationManager.cancelAllNotifications()
|
|
283
|
+
notificationManager.stopRingtone()
|
|
284
|
+
stopSelf()
|
|
285
|
+
}
|
|
144
286
|
else -> {
|
|
145
287
|
Log.e(TAG, "[service] onStartCommand: Unknown action: ${intent.action}")
|
|
146
288
|
stopSelf()
|
|
@@ -158,51 +300,61 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
158
300
|
return super.onUnbind(intent)
|
|
159
301
|
}
|
|
160
302
|
|
|
161
|
-
override fun onCallStateChanged(call: Call) {
|
|
162
|
-
debugLog(
|
|
303
|
+
override fun onCallStateChanged(callId: String, call: Call) {
|
|
304
|
+
debugLog(
|
|
305
|
+
TAG,
|
|
306
|
+
"[service] onCallStateChanged[$callId]: Call state changed: ${call::class.simpleName}"
|
|
307
|
+
)
|
|
163
308
|
when (call) {
|
|
164
309
|
is Call.Registered -> {
|
|
165
310
|
debugLog(
|
|
166
311
|
TAG,
|
|
167
|
-
"[service]
|
|
312
|
+
"[service] onCallStateChanged[$callId]: Call registered - Active: ${call.isActive}, OnHold: ${call.isOnHold}, Muted: ${call.isMuted}"
|
|
168
313
|
)
|
|
169
314
|
|
|
315
|
+
val shouldStopExecution = processPendingActions(call)
|
|
316
|
+
if (shouldStopExecution) {
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
170
320
|
if (call.isIncoming()) {
|
|
171
|
-
if
|
|
172
|
-
|
|
321
|
+
// Play ringtone only if there is no active call
|
|
322
|
+
if (!call.isActive && !callRepository.hasActiveCall(excludeCallId = callId)) {
|
|
323
|
+
notificationManager.startRingtone()
|
|
324
|
+
} else {
|
|
325
|
+
notificationManager.stopRingtone()
|
|
326
|
+
}
|
|
173
327
|
}
|
|
174
|
-
// Update the call
|
|
328
|
+
// Update the call notification
|
|
329
|
+
val notificationId = notificationManager.getOrCreateNotificationId(callId)
|
|
175
330
|
if (isInForeground) {
|
|
176
|
-
notificationManager.updateCallNotification(call)
|
|
331
|
+
notificationManager.updateCallNotification(callId, call)
|
|
177
332
|
} else {
|
|
178
333
|
debugLog(
|
|
179
334
|
TAG,
|
|
180
|
-
"[service]
|
|
335
|
+
"[service] onCallStateChanged[$callId]: Starting foreground for call"
|
|
181
336
|
)
|
|
182
|
-
|
|
183
|
-
val notification = notificationManager.createNotification(call)
|
|
184
|
-
startForegroundSafely(notification)
|
|
337
|
+
notificationManager.resetOptimisticState(callId)
|
|
338
|
+
val notification = notificationManager.createNotification(callId, call)
|
|
339
|
+
startForegroundSafely(notificationId, notification)
|
|
185
340
|
}
|
|
186
341
|
}
|
|
187
|
-
is Call.Unregistered -> {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (isInForeground) {
|
|
191
|
-
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
192
|
-
isInForeground = false
|
|
193
|
-
}
|
|
342
|
+
is Call.None, is Call.Unregistered -> {
|
|
343
|
+
repromoteForegroundIfNeeded(callId)
|
|
344
|
+
if (!callRepository.hasRingingCall()) notificationManager.stopRingtone()
|
|
194
345
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
346
|
+
// Stop service only when no calls remain
|
|
347
|
+
if (!callRepository.hasAnyCalls()) {
|
|
348
|
+
debugLog(
|
|
349
|
+
TAG,
|
|
350
|
+
"[service] onCallStateChanged[$callId]: No more calls, stopping service"
|
|
351
|
+
)
|
|
352
|
+
if (isInForeground) {
|
|
353
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
354
|
+
isInForeground = false
|
|
355
|
+
}
|
|
356
|
+
stopSelf()
|
|
204
357
|
}
|
|
205
|
-
notificationManager.stopRingtone()
|
|
206
358
|
}
|
|
207
359
|
}
|
|
208
360
|
}
|
|
@@ -219,8 +371,6 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
219
371
|
cause: DisconnectCause,
|
|
220
372
|
source: CallRepository.EventSource
|
|
221
373
|
) {
|
|
222
|
-
// we're not passing the callId here to prevent infinite loops
|
|
223
|
-
// callEnd event with callId will sent only when after interaction with notification buttons
|
|
224
374
|
sendBroadcastEvent(CallingxModuleImpl.CALL_END_ACTION) {
|
|
225
375
|
if (callId != null) {
|
|
226
376
|
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
|
|
@@ -268,43 +418,61 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
268
418
|
}
|
|
269
419
|
}
|
|
270
420
|
|
|
271
|
-
|
|
272
|
-
val
|
|
273
|
-
|
|
421
|
+
fun processAction(intent: Intent) {
|
|
422
|
+
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
|
|
423
|
+
val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
424
|
+
intent.getParcelableExtra(EXTRA_ACTION, CallAction::class.java)
|
|
425
|
+
} else {
|
|
426
|
+
@Suppress("DEPRECATION") intent.getParcelableExtra(EXTRA_ACTION)
|
|
427
|
+
} ?: return
|
|
428
|
+
|
|
429
|
+
processAction(callId, action)
|
|
274
430
|
}
|
|
275
431
|
|
|
276
|
-
|
|
277
|
-
debugLog(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
432
|
+
fun processAction(callId: String, action: CallAction) {
|
|
433
|
+
debugLog(
|
|
434
|
+
TAG,
|
|
435
|
+
"[service] processAction[$callId]: Processing action: ${action::class.simpleName}"
|
|
436
|
+
)
|
|
437
|
+
synchronized(actionProcessingLock) {
|
|
438
|
+
val call = callRepository.getCall(callId)
|
|
439
|
+
if (call != null && !call.isPending) {
|
|
440
|
+
call.processAction(action)
|
|
441
|
+
} else {
|
|
442
|
+
// this solves race condition, when action is requested before the call is
|
|
443
|
+
// registered in Telecom
|
|
444
|
+
debugLog(
|
|
445
|
+
TAG,
|
|
446
|
+
"[service] processAction: Add pending action for ${call?.id} to queue"
|
|
447
|
+
)
|
|
448
|
+
CallRegistrationStore.addPendingAction(callId, action)
|
|
449
|
+
}
|
|
286
450
|
}
|
|
287
451
|
}
|
|
288
452
|
|
|
289
|
-
|
|
453
|
+
fun startBackgroundTask(intent: Intent) {
|
|
290
454
|
val taskName = intent.getStringExtra(EXTRA_TASK_NAME)!!
|
|
291
455
|
val data = intent.getBundleExtra(EXTRA_TASK_DATA)!!
|
|
292
456
|
val timeout = intent.getLongExtra(EXTRA_TASK_TIMEOUT, 0)
|
|
293
457
|
headlessJSManager.startHeadlessTask(taskName, data, timeout)
|
|
294
458
|
}
|
|
295
459
|
|
|
296
|
-
|
|
460
|
+
fun stopBackgroundTask() {
|
|
297
461
|
headlessJSManager.stopHeadlessTask()
|
|
298
462
|
}
|
|
299
463
|
|
|
300
464
|
private fun registerCall(intent: Intent, incoming: Boolean) {
|
|
301
465
|
debugLog(TAG, "[service] registerCall: ${if (incoming) "in" else "out"} call")
|
|
466
|
+
|
|
302
467
|
val callInfo = extractIntentParams(intent)
|
|
303
468
|
|
|
304
|
-
// If
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
Log.w(
|
|
469
|
+
// If this specific call is already registered, just notify
|
|
470
|
+
val existingCall = callRepository.getCall(callInfo.callId)
|
|
471
|
+
if (existingCall != null) {
|
|
472
|
+
Log.w(
|
|
473
|
+
TAG,
|
|
474
|
+
"[service] registerCall: Call ${callInfo.callId} already registered, notifying"
|
|
475
|
+
)
|
|
308
476
|
if (incoming) {
|
|
309
477
|
sendBroadcastEvent(CallingxModuleImpl.CALL_REGISTERED_INCOMING_ACTION) {
|
|
310
478
|
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callInfo.callId)
|
|
@@ -316,17 +484,8 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
316
484
|
}
|
|
317
485
|
return
|
|
318
486
|
}
|
|
319
|
-
val tempCall = callRepository.getTempCall(callInfo, incoming)
|
|
320
487
|
|
|
321
|
-
|
|
322
|
-
if (!isInForeground) {
|
|
323
|
-
debugLog(
|
|
324
|
-
TAG,
|
|
325
|
-
"[service] registerCall: Starting foreground for call: ${callInfo.callId}"
|
|
326
|
-
)
|
|
327
|
-
val notification = notificationManager.createNotification(tempCall)
|
|
328
|
-
startForegroundSafely(notification)
|
|
329
|
-
}
|
|
488
|
+
startForegroundForCall(callInfo, incoming)
|
|
330
489
|
|
|
331
490
|
scope.launch {
|
|
332
491
|
try {
|
|
@@ -345,34 +504,59 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
345
504
|
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callInfo.callId)
|
|
346
505
|
}
|
|
347
506
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
507
|
+
repromoteForegroundIfNeeded(callInfo.callId)
|
|
508
|
+
|
|
509
|
+
// Only stop foreground/service when no other calls remain
|
|
510
|
+
if (!callRepository.hasAnyCalls()) {
|
|
511
|
+
if (isInForeground) {
|
|
512
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
513
|
+
isInForeground = false
|
|
514
|
+
}
|
|
515
|
+
notificationManager.stopRingtone()
|
|
516
|
+
stopSelf()
|
|
351
517
|
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
352
521
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
522
|
+
private fun processPendingActions(call: Call.Registered): Boolean {
|
|
523
|
+
synchronized(actionProcessingLock) {
|
|
524
|
+
val pendingActions = CallRegistrationStore.takePendingActions(call.id)
|
|
525
|
+
|
|
526
|
+
val disconnectAction = pendingActions.find { it is CallAction.Disconnect }
|
|
527
|
+
if (disconnectAction != null) {
|
|
528
|
+
// if queue contains Disconnect, execute it and ignore rest of the queue
|
|
529
|
+
debugLog(TAG, "[service] processPendingActions: Executing pending disconnect for ${call.id}")
|
|
530
|
+
call.processAction(disconnectAction)
|
|
531
|
+
return true
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// process pending actions in the order they were added
|
|
535
|
+
for (action in pendingActions) {
|
|
536
|
+
call.processAction(action)
|
|
537
|
+
debugLog(
|
|
538
|
+
TAG,
|
|
539
|
+
"[service] processPendingActions: Executing pending action: $action for ${call.id}"
|
|
540
|
+
)
|
|
356
541
|
}
|
|
542
|
+
|
|
543
|
+
return false
|
|
357
544
|
}
|
|
358
545
|
}
|
|
359
546
|
|
|
360
|
-
private fun startForegroundSafely(notification: Notification) {
|
|
547
|
+
private fun startForegroundSafely(notificationId: Int, notification: Notification) {
|
|
361
548
|
try {
|
|
362
549
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
363
550
|
startForeground(
|
|
364
|
-
|
|
551
|
+
notificationId,
|
|
365
552
|
notification,
|
|
366
553
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
|
|
367
554
|
)
|
|
368
555
|
} else {
|
|
369
|
-
startForeground(
|
|
556
|
+
startForeground(notificationId, notification)
|
|
370
557
|
}
|
|
371
558
|
isInForeground = true
|
|
372
559
|
} catch (e: Exception) {
|
|
373
|
-
// If starting the foreground service fails (for example due to background start
|
|
374
|
-
// restrictions or notification issues), we log the error but avoid crashing the
|
|
375
|
-
// process so the rest of the call flow can continue and be recovered by Telecom.
|
|
376
560
|
Log.e(
|
|
377
561
|
TAG,
|
|
378
562
|
"[service] startForegroundSafely: Failed to start foreground service: ${e.message}",
|
|
@@ -381,6 +565,40 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
381
565
|
}
|
|
382
566
|
}
|
|
383
567
|
|
|
568
|
+
/**
|
|
569
|
+
* Cancels the notification for [callId]. If that notification was the foreground one
|
|
570
|
+
* and other calls remain, re-promotes the service with the next call's notification.
|
|
571
|
+
*/
|
|
572
|
+
private fun repromoteForegroundIfNeeded(callId: String) {
|
|
573
|
+
val newForegroundNotificationId = notificationManager.cancelNotification(callId)
|
|
574
|
+
if (newForegroundNotificationId != null && isInForeground) {
|
|
575
|
+
val newForegroundCallId = notificationManager.getForegroundCallId()
|
|
576
|
+
val call = if (newForegroundCallId != null) callRepository.getCall(newForegroundCallId) else null
|
|
577
|
+
if (call != null && newForegroundCallId != null) {
|
|
578
|
+
debugLog(TAG, "[service] repromoteForegroundIfNeeded: Re-promoting with call $newForegroundCallId (notificationId=$newForegroundNotificationId)")
|
|
579
|
+
val notification = notificationManager.createNotification(newForegroundCallId, call)
|
|
580
|
+
startForegroundSafely(newForegroundNotificationId, notification)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private fun startForegroundForCall(callInfo: CallInfo, incoming: Boolean) {
|
|
586
|
+
val tempCall = callRepository.getTempCall(callInfo, incoming)
|
|
587
|
+
val notificationId = notificationManager.getOrCreateNotificationId(callInfo.callId)
|
|
588
|
+
if (!isInForeground) {
|
|
589
|
+
debugLog(
|
|
590
|
+
TAG,
|
|
591
|
+
"[service] registerCall: Starting foreground for call: ${callInfo.callId}"
|
|
592
|
+
)
|
|
593
|
+
val notification = notificationManager.createNotification(callInfo.callId, tempCall)
|
|
594
|
+
startForegroundSafely(notificationId, notification)
|
|
595
|
+
} else {
|
|
596
|
+
// Already in foreground from another call — just post the notification
|
|
597
|
+
val notification = notificationManager.createNotification(callInfo.callId, tempCall)
|
|
598
|
+
notificationManager.postNotification(callInfo.callId, notification)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
384
602
|
private fun updateCall(intent: Intent) {
|
|
385
603
|
val callInfo = extractIntentParams(intent)
|
|
386
604
|
callRepository.updateCall(
|