@stream-io/react-native-callingx 0.1.0-beta.6 → 0.1.1-beta.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/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 +176 -0
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +302 -80
- package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +176 -191
- 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 +196 -46
- 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 +13 -10
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/spec/NativeCallingx.js.map +1 -1
- package/dist/module/utils/constants.js +24 -13
- package/dist/module/utils/constants.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +3 -0
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts +7 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +31 -0
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/constants.d.ts +1 -1
- 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 +14 -10
- package/src/spec/NativeCallingx.ts +10 -3
- package/src/types.ts +34 -0
- package/src/utils/constants.ts +23 -9
- package/android/bin/build/generated/source/buildConfig/debug/io/getstream/rn/callingx/BuildConfig.class +0 -0
- package/android/bin/build/generated/source/codegen/java/io/getstream/rn/callingx/NativeCallingxSpec.class +0 -0
- package/android/bin/build/generated/source/codegen/jni/CMakeLists.txt +0 -28
- package/android/bin/build/generated/source/codegen/jni/CallingxSpec-generated.cpp +0 -167
- package/android/bin/build/generated/source/codegen/jni/CallingxSpec.h +0 -31
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/CallingxSpecJSI-generated.cpp +0 -196
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/CallingxSpecJSI.h +0 -283
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/ComponentDescriptors.cpp +0 -22
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/ComponentDescriptors.h +0 -24
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/EventEmitters.cpp +0 -16
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/EventEmitters.h +0 -17
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/Props.cpp +0 -19
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/Props.h +0 -18
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/ShadowNodes.cpp +0 -17
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/ShadowNodes.h +0 -23
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/States.cpp +0 -16
- package/android/bin/build/generated/source/codegen/jni/react/renderer/components/CallingxSpec/States.h +0 -20
- package/android/bin/build/generated/source/codegen/schema.json +0 -1
- package/android/bin/src/main/AndroidManifest.xml +0 -29
- package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/NotificationChannelsManager.kt +0 -104
- /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
|
|
@@ -59,6 +67,59 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
59
67
|
internal const val ACTION_STOP_BACKGROUND_TASK = "stop_background_task"
|
|
60
68
|
internal const val ACTION_STOP_SERVICE = "stop_service"
|
|
61
69
|
internal const val ACTION_REGISTRATION_FAILED = "registration_failed"
|
|
70
|
+
|
|
71
|
+
fun startIncomingCallFromPush(context: Context, data: Map<String, String>) {
|
|
72
|
+
debugLog(TAG, "[service] startIncomingCallFromPush: Starting incoming call from push")
|
|
73
|
+
|
|
74
|
+
// Check if we are allowed to post call notifications (moved from JS layer).
|
|
75
|
+
val notificationsConfig = NotificationsConfig.loadNotificationsConfig(context)
|
|
76
|
+
val notificationChannelsManager =
|
|
77
|
+
NotificationChannelsManager(context).apply {
|
|
78
|
+
setNotificationsConfig(notificationsConfig)
|
|
79
|
+
}
|
|
80
|
+
val notificationStatus = notificationChannelsManager.getNotificationStatus()
|
|
81
|
+
if (!notificationStatus.canPost) {
|
|
82
|
+
debugLog(
|
|
83
|
+
TAG,
|
|
84
|
+
"[service] startIncomingCallFromPush: Cannot post notifications, skipping incoming call"
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
val shouldRejectCallWhenBusy = SettingsStore.shouldRejectCallWhenBusy(context)
|
|
90
|
+
if (shouldRejectCallWhenBusy && CallRegistrationStore.hasRegisteredCall()) {
|
|
91
|
+
debugLog(
|
|
92
|
+
TAG,
|
|
93
|
+
"[service] startIncomingCallFromPush: Registered call found and rejectCallWhenBusy is enabled, skipping incoming call"
|
|
94
|
+
)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
val callCid = data["call_cid"]
|
|
99
|
+
if (callCid.isNullOrEmpty()) {
|
|
100
|
+
debugLog(
|
|
101
|
+
TAG,
|
|
102
|
+
"[service] startIncomingCallFromPush: Call CID is null or empty, skipping"
|
|
103
|
+
)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
val callName = data["created_by_display_name"].orEmpty()
|
|
108
|
+
val isVideo = data["video"] == "true"
|
|
109
|
+
|
|
110
|
+
CallRegistrationStore.trackCallRegistration(callCid, null)
|
|
111
|
+
|
|
112
|
+
val intent =
|
|
113
|
+
Intent(context, CallService::class.java).apply {
|
|
114
|
+
action = ACTION_INCOMING_CALL
|
|
115
|
+
putExtra(EXTRA_CALL_ID, callCid)
|
|
116
|
+
putExtra(EXTRA_URI, callCid.toUri())
|
|
117
|
+
putExtra(EXTRA_NAME, callName)
|
|
118
|
+
putExtra(EXTRA_IS_VIDEO, isVideo)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
ContextCompat.startForegroundService(context, intent)
|
|
122
|
+
}
|
|
62
123
|
}
|
|
63
124
|
|
|
64
125
|
inner class CallServiceBinder : Binder() {
|
|
@@ -71,9 +132,72 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
71
132
|
|
|
72
133
|
private val binder = CallServiceBinder()
|
|
73
134
|
private val scope: CoroutineScope = CoroutineScope(SupervisorJob())
|
|
135
|
+
private val actionProcessingLock = Object()
|
|
74
136
|
|
|
75
137
|
private var isInForeground = false
|
|
76
138
|
|
|
139
|
+
private val optimisticNotificationReceiver =
|
|
140
|
+
object : BroadcastReceiver() {
|
|
141
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
142
|
+
val callId = intent.getStringExtra(CallingxModuleImpl.EXTRA_CALL_ID) ?: return
|
|
143
|
+
when (intent.action) {
|
|
144
|
+
CallingxModuleImpl.CALL_OPTIMISTIC_ACCEPT_ACTION -> {
|
|
145
|
+
debugLog(
|
|
146
|
+
TAG,
|
|
147
|
+
"[service] optimisticReceiver: Optimistic accept for $callId"
|
|
148
|
+
)
|
|
149
|
+
notificationManager.stopRingtone()
|
|
150
|
+
notificationManager.setOptimisticState(
|
|
151
|
+
callId,
|
|
152
|
+
CallNotificationManager.OptimisticState.ACCEPTING
|
|
153
|
+
)
|
|
154
|
+
val call = callRepository.getCall(callId)
|
|
155
|
+
if (call != null) {
|
|
156
|
+
notificationManager.updateCallNotification(callId, call)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
CallingxModuleImpl.CALL_END_ACTION -> {
|
|
160
|
+
val source = intent.getStringExtra(CallingxModuleImpl.EXTRA_SOURCE)
|
|
161
|
+
val cause =
|
|
162
|
+
intent.getStringExtra(CallingxModuleImpl.EXTRA_DISCONNECT_CAUSE)
|
|
163
|
+
val rejectedCause =
|
|
164
|
+
getDisconnectCauseString(
|
|
165
|
+
DisconnectCause(DisconnectCause.REJECTED)
|
|
166
|
+
)
|
|
167
|
+
val call = callRepository.getCall(callId)
|
|
168
|
+
|
|
169
|
+
val isSysSource =
|
|
170
|
+
source == CallRepository.EventSource.SYS.name.lowercase()
|
|
171
|
+
|
|
172
|
+
// we handle optimistic updates only if incoming call (non-answered) was rejected within notification action
|
|
173
|
+
if (!isSysSource ||
|
|
174
|
+
cause != rejectedCause ||
|
|
175
|
+
call == null ||
|
|
176
|
+
!call.isIncoming() ||
|
|
177
|
+
call.isActive
|
|
178
|
+
) {
|
|
179
|
+
debugLog(
|
|
180
|
+
TAG,
|
|
181
|
+
"[service] optimisticReceiver: Skipping optimistic reject for $callId"
|
|
182
|
+
)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
debugLog(
|
|
187
|
+
TAG,
|
|
188
|
+
"[service] optimisticReceiver: Optimistic reject for $callId"
|
|
189
|
+
)
|
|
190
|
+
notificationManager.stopRingtone()
|
|
191
|
+
notificationManager.setOptimisticState(
|
|
192
|
+
callId,
|
|
193
|
+
CallNotificationManager.OptimisticState.REJECTING
|
|
194
|
+
)
|
|
195
|
+
notificationManager.updateCallNotification(callId, call)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
77
201
|
override fun onCreate() {
|
|
78
202
|
super.onCreate()
|
|
79
203
|
debugLog(TAG, "[service] onCreate: TelecomCallService created")
|
|
@@ -83,6 +207,18 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
83
207
|
callRepository = CallRepositoryFactory.create(applicationContext)
|
|
84
208
|
callRepository.setListener(this)
|
|
85
209
|
|
|
210
|
+
val filter =
|
|
211
|
+
IntentFilter().apply {
|
|
212
|
+
addAction(CallingxModuleImpl.CALL_OPTIMISTIC_ACCEPT_ACTION)
|
|
213
|
+
addAction(CallingxModuleImpl.CALL_END_ACTION)
|
|
214
|
+
}
|
|
215
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
216
|
+
registerReceiver(optimisticNotificationReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
|
217
|
+
} else {
|
|
218
|
+
@Suppress("UnspecifiedRegisterReceiverFlag")
|
|
219
|
+
registerReceiver(optimisticNotificationReceiver, filter)
|
|
220
|
+
}
|
|
221
|
+
|
|
86
222
|
sendBroadcastEvent(CallingxModuleImpl.SERVICE_READY_ACTION)
|
|
87
223
|
}
|
|
88
224
|
|
|
@@ -90,7 +226,9 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
90
226
|
super.onDestroy()
|
|
91
227
|
debugLog(TAG, "[service] onDestroy: TelecomCallService destroyed")
|
|
92
228
|
|
|
93
|
-
|
|
229
|
+
unregisterReceiver(optimisticNotificationReceiver)
|
|
230
|
+
|
|
231
|
+
notificationManager.cancelAllNotifications()
|
|
94
232
|
notificationManager.stopRingtone()
|
|
95
233
|
callRepository.release()
|
|
96
234
|
headlessJSManager.release()
|
|
@@ -124,23 +262,25 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
124
262
|
registerCall(intent, false)
|
|
125
263
|
}
|
|
126
264
|
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
265
|
startBackgroundTask(intent)
|
|
266
|
+
return START_NOT_STICKY
|
|
137
267
|
}
|
|
138
268
|
ACTION_STOP_BACKGROUND_TASK -> {
|
|
139
269
|
stopBackgroundTask()
|
|
270
|
+
return START_NOT_STICKY
|
|
140
271
|
}
|
|
141
272
|
ACTION_UPDATE_CALL -> {
|
|
142
273
|
updateCall(intent)
|
|
143
274
|
}
|
|
275
|
+
ACTION_STOP_SERVICE -> {
|
|
276
|
+
if (isInForeground) {
|
|
277
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
278
|
+
isInForeground = false
|
|
279
|
+
}
|
|
280
|
+
notificationManager.cancelAllNotifications()
|
|
281
|
+
notificationManager.stopRingtone()
|
|
282
|
+
stopSelf()
|
|
283
|
+
}
|
|
144
284
|
else -> {
|
|
145
285
|
Log.e(TAG, "[service] onStartCommand: Unknown action: ${intent.action}")
|
|
146
286
|
stopSelf()
|
|
@@ -158,51 +298,61 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
158
298
|
return super.onUnbind(intent)
|
|
159
299
|
}
|
|
160
300
|
|
|
161
|
-
override fun onCallStateChanged(call: Call) {
|
|
162
|
-
debugLog(
|
|
301
|
+
override fun onCallStateChanged(callId: String, call: Call) {
|
|
302
|
+
debugLog(
|
|
303
|
+
TAG,
|
|
304
|
+
"[service] onCallStateChanged[$callId]: Call state changed: ${call::class.simpleName}"
|
|
305
|
+
)
|
|
163
306
|
when (call) {
|
|
164
307
|
is Call.Registered -> {
|
|
165
308
|
debugLog(
|
|
166
309
|
TAG,
|
|
167
|
-
"[service]
|
|
310
|
+
"[service] onCallStateChanged[$callId]: Call registered - Active: ${call.isActive}, OnHold: ${call.isOnHold}, Muted: ${call.isMuted}"
|
|
168
311
|
)
|
|
169
312
|
|
|
313
|
+
val shouldStopExecution = processPendingActions(call)
|
|
314
|
+
if (shouldStopExecution) {
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
170
318
|
if (call.isIncoming()) {
|
|
171
|
-
if
|
|
172
|
-
|
|
319
|
+
// Play ringtone only if there is no active call
|
|
320
|
+
if (!call.isActive && !callRepository.hasActiveCall(excludeCallId = callId)) {
|
|
321
|
+
notificationManager.startRingtone()
|
|
322
|
+
} else {
|
|
323
|
+
notificationManager.stopRingtone()
|
|
324
|
+
}
|
|
173
325
|
}
|
|
174
|
-
// Update the call
|
|
326
|
+
// Update the call notification
|
|
327
|
+
val notificationId = notificationManager.getOrCreateNotificationId(callId)
|
|
175
328
|
if (isInForeground) {
|
|
176
|
-
notificationManager.updateCallNotification(call)
|
|
329
|
+
notificationManager.updateCallNotification(callId, call)
|
|
177
330
|
} else {
|
|
178
331
|
debugLog(
|
|
179
332
|
TAG,
|
|
180
|
-
"[service]
|
|
333
|
+
"[service] onCallStateChanged[$callId]: Starting foreground for call"
|
|
181
334
|
)
|
|
182
|
-
|
|
183
|
-
val notification = notificationManager.createNotification(call)
|
|
184
|
-
startForegroundSafely(notification)
|
|
335
|
+
notificationManager.resetOptimisticState(callId)
|
|
336
|
+
val notification = notificationManager.createNotification(callId, call)
|
|
337
|
+
startForegroundSafely(notificationId, notification)
|
|
185
338
|
}
|
|
186
339
|
}
|
|
187
|
-
is Call.Unregistered -> {
|
|
188
|
-
|
|
340
|
+
is Call.None, is Call.Unregistered -> {
|
|
341
|
+
repromoteForegroundIfNeeded(callId)
|
|
342
|
+
if (!callRepository.hasRingingCall()) notificationManager.stopRingtone()
|
|
189
343
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (isInForeground) {
|
|
202
|
-
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
203
|
-
isInForeground = false
|
|
344
|
+
// Stop service only when no calls remain
|
|
345
|
+
if (!callRepository.hasAnyCalls()) {
|
|
346
|
+
debugLog(
|
|
347
|
+
TAG,
|
|
348
|
+
"[service] onCallStateChanged[$callId]: No more calls, stopping service"
|
|
349
|
+
)
|
|
350
|
+
if (isInForeground) {
|
|
351
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
352
|
+
isInForeground = false
|
|
353
|
+
}
|
|
354
|
+
stopSelf()
|
|
204
355
|
}
|
|
205
|
-
notificationManager.stopRingtone()
|
|
206
356
|
}
|
|
207
357
|
}
|
|
208
358
|
}
|
|
@@ -219,8 +369,6 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
219
369
|
cause: DisconnectCause,
|
|
220
370
|
source: CallRepository.EventSource
|
|
221
371
|
) {
|
|
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
372
|
sendBroadcastEvent(CallingxModuleImpl.CALL_END_ACTION) {
|
|
225
373
|
if (callId != null) {
|
|
226
374
|
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
|
|
@@ -268,21 +416,34 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
268
416
|
}
|
|
269
417
|
}
|
|
270
418
|
|
|
271
|
-
public fun hasRegisteredCall(): Boolean {
|
|
272
|
-
val currentCall = callRepository.currentCall.value
|
|
273
|
-
return currentCall is Call.Registered
|
|
274
|
-
}
|
|
275
|
-
|
|
276
419
|
public fun processAction(callId: String, action: CallAction) {
|
|
277
|
-
debugLog(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
420
|
+
debugLog(
|
|
421
|
+
TAG,
|
|
422
|
+
"[service] processAction[$callId]: Processing action: ${action::class.simpleName}"
|
|
423
|
+
)
|
|
424
|
+
synchronized(actionProcessingLock) {
|
|
425
|
+
val call = callRepository.getCall(callId)
|
|
426
|
+
if (call != null && !call.isPending) {
|
|
427
|
+
call.processAction(action)
|
|
428
|
+
} else {
|
|
429
|
+
// this solves race condition, when action is requested before the call is
|
|
430
|
+
// registered in Telecom
|
|
431
|
+
if (action is CallAction.Disconnect) {
|
|
432
|
+
debugLog(TAG, "[service] storing pending disconnect for $callId")
|
|
433
|
+
CallRegistrationStore.setPendingDisconnect(callId, action.cause.code)
|
|
434
|
+
} else if (action is CallAction.Answer) {
|
|
435
|
+
debugLog(TAG, "[service] storing pending answer for $callId")
|
|
436
|
+
CallRegistrationStore.setPendingAnswer(callId, action.isAudioCall)
|
|
437
|
+
} else if (action is CallAction.ToggleMute) {
|
|
438
|
+
debugLog(TAG, "[service] storing pending mute for $callId")
|
|
439
|
+
CallRegistrationStore.setPendingMute(callId, action.isMute)
|
|
440
|
+
} else {
|
|
441
|
+
Log.w(
|
|
442
|
+
TAG,
|
|
443
|
+
"[service] processAction[$callId]: Call not registered, ignoring action"
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
286
447
|
}
|
|
287
448
|
}
|
|
288
449
|
|
|
@@ -299,12 +460,16 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
299
460
|
|
|
300
461
|
private fun registerCall(intent: Intent, incoming: Boolean) {
|
|
301
462
|
debugLog(TAG, "[service] registerCall: ${if (incoming) "in" else "out"} call")
|
|
463
|
+
|
|
302
464
|
val callInfo = extractIntentParams(intent)
|
|
303
465
|
|
|
304
|
-
// If
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
Log.w(
|
|
466
|
+
// If this specific call is already registered, just notify
|
|
467
|
+
val existingCall = callRepository.getCall(callInfo.callId)
|
|
468
|
+
if (existingCall != null) {
|
|
469
|
+
Log.w(
|
|
470
|
+
TAG,
|
|
471
|
+
"[service] registerCall: Call ${callInfo.callId} already registered, notifying"
|
|
472
|
+
)
|
|
308
473
|
if (incoming) {
|
|
309
474
|
sendBroadcastEvent(CallingxModuleImpl.CALL_REGISTERED_INCOMING_ACTION) {
|
|
310
475
|
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callInfo.callId)
|
|
@@ -316,17 +481,8 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
316
481
|
}
|
|
317
482
|
return
|
|
318
483
|
}
|
|
319
|
-
val tempCall = callRepository.getTempCall(callInfo, incoming)
|
|
320
484
|
|
|
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
|
-
}
|
|
485
|
+
startForegroundForCall(callInfo, incoming)
|
|
330
486
|
|
|
331
487
|
scope.launch {
|
|
332
488
|
try {
|
|
@@ -345,34 +501,66 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
345
501
|
putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callInfo.callId)
|
|
346
502
|
}
|
|
347
503
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
504
|
+
repromoteForegroundIfNeeded(callInfo.callId)
|
|
505
|
+
|
|
506
|
+
// Only stop foreground/service when no other calls remain
|
|
507
|
+
if (!callRepository.hasAnyCalls()) {
|
|
508
|
+
if (isInForeground) {
|
|
509
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
510
|
+
isInForeground = false
|
|
511
|
+
}
|
|
512
|
+
notificationManager.stopRingtone()
|
|
513
|
+
stopSelf()
|
|
351
514
|
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
352
518
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
519
|
+
private fun processPendingActions(call: Call.Registered): Boolean {
|
|
520
|
+
synchronized(actionProcessingLock) {
|
|
521
|
+
val pendingCauseCode = CallRegistrationStore.takePendingDisconnect(call.id)
|
|
522
|
+
val pendingAnswer = CallRegistrationStore.takePendingAnswer(call.id)
|
|
523
|
+
val pendingMute = CallRegistrationStore.takePendingMute(call.id)
|
|
524
|
+
|
|
525
|
+
if (pendingCauseCode != null) {
|
|
526
|
+
debugLog(
|
|
527
|
+
TAG,
|
|
528
|
+
"[service] onCallStateChanged: Executing pending disconnect for ${call.id}"
|
|
529
|
+
)
|
|
530
|
+
call.processAction(CallAction.Disconnect(DisconnectCause(pendingCauseCode)))
|
|
531
|
+
return true
|
|
356
532
|
}
|
|
533
|
+
|
|
534
|
+
if (pendingAnswer != null) {
|
|
535
|
+
debugLog(
|
|
536
|
+
TAG,
|
|
537
|
+
"[service] onCallStateChanged: Executing pending answer for ${call.id}"
|
|
538
|
+
)
|
|
539
|
+
call.processAction(CallAction.Answer(pendingAnswer))
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (pendingMute != null) {
|
|
543
|
+
debugLog(TAG, "[service] onCallStateChanged: Executing pending mute for ${call.id}")
|
|
544
|
+
call.processAction(CallAction.ToggleMute(pendingMute))
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return false
|
|
357
548
|
}
|
|
358
549
|
}
|
|
359
550
|
|
|
360
|
-
private fun startForegroundSafely(notification: Notification) {
|
|
551
|
+
private fun startForegroundSafely(notificationId: Int, notification: Notification) {
|
|
361
552
|
try {
|
|
362
553
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
363
554
|
startForeground(
|
|
364
|
-
|
|
555
|
+
notificationId,
|
|
365
556
|
notification,
|
|
366
557
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
|
|
367
558
|
)
|
|
368
559
|
} else {
|
|
369
|
-
startForeground(
|
|
560
|
+
startForeground(notificationId, notification)
|
|
370
561
|
}
|
|
371
562
|
isInForeground = true
|
|
372
563
|
} 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
564
|
Log.e(
|
|
377
565
|
TAG,
|
|
378
566
|
"[service] startForegroundSafely: Failed to start foreground service: ${e.message}",
|
|
@@ -381,6 +569,40 @@ class CallService : Service(), CallRepository.Listener {
|
|
|
381
569
|
}
|
|
382
570
|
}
|
|
383
571
|
|
|
572
|
+
/**
|
|
573
|
+
* Cancels the notification for [callId]. If that notification was the foreground one
|
|
574
|
+
* and other calls remain, re-promotes the service with the next call's notification.
|
|
575
|
+
*/
|
|
576
|
+
private fun repromoteForegroundIfNeeded(callId: String) {
|
|
577
|
+
val newForegroundNotificationId = notificationManager.cancelNotification(callId)
|
|
578
|
+
if (newForegroundNotificationId != null && isInForeground) {
|
|
579
|
+
val newForegroundCallId = notificationManager.getForegroundCallId()
|
|
580
|
+
val call = if (newForegroundCallId != null) callRepository.getCall(newForegroundCallId) else null
|
|
581
|
+
if (call != null && newForegroundCallId != null) {
|
|
582
|
+
debugLog(TAG, "[service] repromoteForegroundIfNeeded: Re-promoting with call $newForegroundCallId (notificationId=$newForegroundNotificationId)")
|
|
583
|
+
val notification = notificationManager.createNotification(newForegroundCallId, call)
|
|
584
|
+
startForegroundSafely(newForegroundNotificationId, notification)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private fun startForegroundForCall(callInfo: CallInfo, incoming: Boolean) {
|
|
590
|
+
val tempCall = callRepository.getTempCall(callInfo, incoming)
|
|
591
|
+
val notificationId = notificationManager.getOrCreateNotificationId(callInfo.callId)
|
|
592
|
+
if (!isInForeground) {
|
|
593
|
+
debugLog(
|
|
594
|
+
TAG,
|
|
595
|
+
"[service] registerCall: Starting foreground for call: ${callInfo.callId}"
|
|
596
|
+
)
|
|
597
|
+
val notification = notificationManager.createNotification(callInfo.callId, tempCall)
|
|
598
|
+
startForegroundSafely(notificationId, notification)
|
|
599
|
+
} else {
|
|
600
|
+
// Already in foreground from another call — just post the notification
|
|
601
|
+
val notification = notificationManager.createNotification(callInfo.callId, tempCall)
|
|
602
|
+
notificationManager.postNotification(callInfo.callId, notification)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
384
606
|
private fun updateCall(intent: Intent) {
|
|
385
607
|
val callInfo = extractIntentParams(intent)
|
|
386
608
|
callRepository.updateCall(
|