@capgo/capacitor-stream-call 0.0.82 → 0.0.84
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/CapgoCapacitorStreamCall.podspec +2 -2
- package/Package.swift +1 -1
- package/android/build.gradle +2 -2
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +2 -2
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallFragment.kt +8 -36
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +441 -361
- package/android/src/main/java/ee/forgr/capacitor/streamcall/TouchInterceptWrapper.kt +5 -3
- package/android/src/main/java/ee/forgr/capacitor/streamcall/UserRepository.kt +4 -5
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +1 -5
- package/package.json +1 -1
- package/android/src/main/java/ee/forgr/capacitor/streamcall/RingtonePlayer.kt +0 -162
|
@@ -20,15 +20,14 @@ import android.os.Bundle
|
|
|
20
20
|
import android.os.Handler
|
|
21
21
|
import android.os.Looper
|
|
22
22
|
import android.provider.Settings
|
|
23
|
+
import android.util.Log
|
|
23
24
|
import android.view.View
|
|
24
25
|
import android.view.ViewGroup
|
|
25
26
|
import android.view.WindowManager
|
|
26
27
|
import android.widget.FrameLayout
|
|
27
28
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
28
29
|
import androidx.compose.runtime.collectAsState
|
|
29
|
-
import androidx.compose.runtime.derivedStateOf
|
|
30
30
|
import androidx.compose.runtime.getValue
|
|
31
|
-
import androidx.compose.runtime.remember
|
|
32
31
|
import androidx.compose.ui.Modifier
|
|
33
32
|
import androidx.compose.ui.draw.clip
|
|
34
33
|
import androidx.compose.ui.platform.ComposeView
|
|
@@ -87,11 +86,13 @@ import kotlinx.coroutines.launch
|
|
|
87
86
|
import kotlinx.coroutines.tasks.await
|
|
88
87
|
import androidx.core.net.toUri
|
|
89
88
|
import org.json.JSONObject
|
|
89
|
+
import androidx.core.graphics.toColorInt
|
|
90
|
+
import androidx.core.content.edit
|
|
90
91
|
|
|
91
92
|
// I am not a religious pearson, but at this point, I am not sure even god himself would understand this code
|
|
92
93
|
// It's a spaghetti-like, tangled, unreadable mess and frankly, I am deeply sorry for the code crimes commited in the Android impl
|
|
93
94
|
@CapacitorPlugin(name = "StreamCall")
|
|
94
|
-
|
|
95
|
+
class StreamCallPlugin : Plugin() {
|
|
95
96
|
private var streamVideoClient: StreamVideo? = null
|
|
96
97
|
private var state: State = State.NOT_INITIALIZED
|
|
97
98
|
private var overlayView: ComposeView? = null
|
|
@@ -108,6 +109,8 @@ public class StreamCallPlugin : Plugin() {
|
|
|
108
109
|
private var activeCallStateJob: Job? = null
|
|
109
110
|
private var cameraStatusJob: Job? = null
|
|
110
111
|
private var microphoneStatusJob: Job? = null
|
|
112
|
+
private var lastEventSent: String? = null
|
|
113
|
+
private var callIsAudioOnly: Boolean = false
|
|
111
114
|
|
|
112
115
|
// Store current call info
|
|
113
116
|
private var currentCallId: String = ""
|
|
@@ -116,7 +119,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
116
119
|
|
|
117
120
|
// Add a field for the fragment
|
|
118
121
|
private var callFragment: StreamCallFragment? = null
|
|
119
|
-
private var streamVideo: StreamVideo? = null
|
|
120
122
|
private var touchInterceptWrapper: TouchInterceptWrapper? = null
|
|
121
123
|
|
|
122
124
|
// Track permission request timing and attempts
|
|
@@ -131,6 +133,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
131
133
|
private var pendingCallTeam: String? = null
|
|
132
134
|
private var pendingCustomObject: JSObject? = null
|
|
133
135
|
private var pendingAcceptCall: Call? = null // Store the actual call object for acceptance
|
|
136
|
+
private var pendingSetCameraCall: PluginCall? = null
|
|
134
137
|
|
|
135
138
|
private enum class State {
|
|
136
139
|
NOT_INITIALIZED,
|
|
@@ -138,7 +141,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
138
141
|
INITIALIZED
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
|
|
144
|
+
fun incomingOnlyRingingConfig(): RingingConfig = object : RingingConfig {
|
|
142
145
|
override val incomingCallSoundUri: Uri? = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
143
146
|
override val outgoingCallSoundUri: Uri? = null
|
|
144
147
|
}
|
|
@@ -154,83 +157,92 @@ public class StreamCallPlugin : Plugin() {
|
|
|
154
157
|
override fun handleOnResume() {
|
|
155
158
|
super.handleOnResume()
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
Log.d("StreamCallPlugin", "handleOnResume: App resumed, checking permissions and pending operations")
|
|
161
|
+
Log.d("StreamCallPlugin", "handleOnResume: Have pendingCall: ${pendingCall != null}")
|
|
162
|
+
Log.d("StreamCallPlugin", "handleOnResume: Have pendingCallUserIds: ${pendingCallUserIds != null}")
|
|
163
|
+
Log.d("StreamCallPlugin", "handleOnResume: Have pendingAcceptCall: ${pendingAcceptCall != null}")
|
|
164
|
+
Log.d("StreamCallPlugin", "handleOnResume: Permission attempt count: $permissionAttemptCount")
|
|
162
165
|
|
|
163
166
|
// Check if permissions were granted after returning from settings or permission dialog
|
|
164
|
-
if (checkPermissions()) {
|
|
165
|
-
|
|
167
|
+
if (checkPermissions(this.callIsAudioOnly)) {
|
|
168
|
+
Log.d("StreamCallPlugin", "handleOnResume: Permissions are now granted")
|
|
166
169
|
// Handle any pending calls that were waiting for permissions
|
|
167
170
|
handlePermissionGranted()
|
|
168
171
|
} else if (pendingCall != null || pendingAcceptCall != null) {
|
|
169
|
-
|
|
172
|
+
Log.d("StreamCallPlugin", "handleOnResume: Permissions still not granted, but have pending operations")
|
|
170
173
|
// If we have pending operations but permissions are still not granted,
|
|
171
174
|
// it means the permission dialog was dismissed without granting
|
|
172
175
|
// We should trigger our retry logic if we haven't exhausted attempts
|
|
173
176
|
if (permissionAttemptCount > 0) {
|
|
174
|
-
|
|
177
|
+
Log.d("StreamCallPlugin", "handleOnResume: Permission dialog was dismissed, treating as denial (attempt: $permissionAttemptCount)")
|
|
175
178
|
val timeSinceRequest = System.currentTimeMillis() - permissionRequestStartTime
|
|
176
179
|
handlePermissionDenied(timeSinceRequest)
|
|
177
180
|
} else {
|
|
178
|
-
|
|
181
|
+
Log.d("StreamCallPlugin", "handleOnResume: No permission attempts yet, starting permission request")
|
|
179
182
|
// If we have pending operations but no attempts yet, start the permission flow
|
|
180
183
|
if (pendingAcceptCall != null) {
|
|
181
|
-
|
|
184
|
+
Log.d("StreamCallPlugin", "handleOnResume: Have active call waiting for permissions, requesting now")
|
|
182
185
|
permissionAttemptCount = 0
|
|
183
|
-
requestPermissions()
|
|
186
|
+
requestPermissions(this.callIsAudioOnly)
|
|
184
187
|
} else if (pendingCall != null && pendingCallUserIds != null) {
|
|
185
|
-
|
|
188
|
+
Log.d("StreamCallPlugin", "handleOnResume: Have outgoing call waiting for permissions, requesting now")
|
|
186
189
|
permissionAttemptCount = 0
|
|
187
|
-
requestPermissions()
|
|
190
|
+
requestPermissions(this.callIsAudioOnly)
|
|
188
191
|
}
|
|
189
192
|
}
|
|
190
193
|
} else {
|
|
191
|
-
|
|
194
|
+
Log.d("StreamCallPlugin", "handleOnResume: No pending operations, nothing to handle")
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
override fun load() {
|
|
199
|
+
try {
|
|
200
|
+
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
201
|
+
if (packageInfo.firstInstallTime == packageInfo.lastUpdateTime) {
|
|
202
|
+
Log.d("StreamCallPlugin", "Fresh install detected, clearing user credentials.")
|
|
203
|
+
SecureUserRepository.getInstance(context).removeCurrentUser()
|
|
204
|
+
}
|
|
205
|
+
} catch (e: Exception) {
|
|
206
|
+
Log.e("StreamCallPlugin", "Error checking for fresh install", e)
|
|
207
|
+
}
|
|
196
208
|
// general init
|
|
197
209
|
initializeStreamVideo()
|
|
198
210
|
setupViews()
|
|
199
211
|
super.load()
|
|
200
|
-
checkPermissions()
|
|
212
|
+
checkPermissions(this.callIsAudioOnly)
|
|
201
213
|
// Register broadcast receiver for ACCEPT_CALL action with high priority
|
|
202
214
|
val filter = IntentFilter("io.getstream.video.android.action.ACCEPT_CALL")
|
|
203
215
|
filter.priority = 999 // Set high priority to ensure it captures the intent
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
ContextCompat.registerReceiver(activity, acceptCallReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
|
217
|
+
Log.d("StreamCallPlugin", "Registered broadcast receiver for ACCEPT_CALL action with high priority")
|
|
206
218
|
|
|
207
219
|
// Start the background service to keep the app alive
|
|
208
220
|
val serviceIntent = Intent(activity, StreamCallBackgroundService::class.java)
|
|
209
221
|
activity.startService(serviceIntent)
|
|
210
|
-
|
|
222
|
+
Log.d("StreamCallPlugin", "Started StreamCallBackgroundService to keep app alive")
|
|
211
223
|
}
|
|
212
224
|
|
|
213
225
|
@OptIn(DelicateCoroutinesApi::class)
|
|
214
|
-
override fun handleOnNewIntent(intent:
|
|
215
|
-
|
|
226
|
+
override fun handleOnNewIntent(intent: Intent) {
|
|
227
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent called: action=${intent.action}, data=${intent.data}, extras=${intent.extras}")
|
|
216
228
|
super.handleOnNewIntent(intent)
|
|
217
229
|
|
|
218
230
|
val action = intent.action
|
|
219
231
|
val data = intent.data
|
|
220
232
|
val extras = intent.extras
|
|
221
|
-
|
|
233
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: Parsed action: $action")
|
|
222
234
|
|
|
223
235
|
if (action === "io.getstream.video.android.action.INCOMING_CALL") {
|
|
224
|
-
|
|
236
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: Matched INCOMING_CALL action")
|
|
225
237
|
// We need to make sure the activity is visible on locked screen in such case
|
|
226
238
|
changeActivityAsVisibleOnLockScreen(this@StreamCallPlugin.activity, true)
|
|
227
239
|
activity?.runOnUiThread {
|
|
228
240
|
val cid = intent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
|
|
229
|
-
|
|
241
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - Extracted cid: $cid")
|
|
230
242
|
if (cid != null) {
|
|
231
|
-
|
|
243
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - cid is not null, processing.")
|
|
232
244
|
val call = streamVideoClient?.call(id = cid.id, type = cid.type)
|
|
233
|
-
|
|
245
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - Got call object: ${call?.id}")
|
|
234
246
|
|
|
235
247
|
// Try to get caller information from the call
|
|
236
248
|
kotlinx.coroutines.GlobalScope.launch {
|
|
@@ -239,15 +251,15 @@ public class StreamCallPlugin : Plugin() {
|
|
|
239
251
|
val callerInfo = callInfo?.getOrNull()?.call?.createdBy
|
|
240
252
|
val custom = callInfo?.getOrNull()?.call?.custom
|
|
241
253
|
|
|
242
|
-
val payload =
|
|
254
|
+
val payload = JSObject().apply {
|
|
243
255
|
put("cid", cid.cid)
|
|
244
256
|
put("type", "incoming")
|
|
245
257
|
if (callerInfo != null) {
|
|
246
|
-
val caller =
|
|
258
|
+
val caller = JSObject().apply {
|
|
247
259
|
put("userId", callerInfo.id)
|
|
248
260
|
put("name", callerInfo.name ?: "")
|
|
249
261
|
put("imageURL", callerInfo.image ?: "")
|
|
250
|
-
put("role", callerInfo.role
|
|
262
|
+
put("role", callerInfo.role)
|
|
251
263
|
}
|
|
252
264
|
put("caller", caller)
|
|
253
265
|
}
|
|
@@ -263,9 +275,9 @@ public class StreamCallPlugin : Plugin() {
|
|
|
263
275
|
kotlinx.coroutines.delay(500) // 500ms delay
|
|
264
276
|
bringAppToForeground()
|
|
265
277
|
} catch (e: Exception) {
|
|
266
|
-
|
|
278
|
+
Log.e("StreamCallPlugin", "Error getting call info for incoming call", e)
|
|
267
279
|
// Fallback to basic payload without caller info
|
|
268
|
-
val payload =
|
|
280
|
+
val payload = JSObject().apply {
|
|
269
281
|
put("cid", cid.cid)
|
|
270
282
|
put("type", "incoming")
|
|
271
283
|
}
|
|
@@ -277,41 +289,43 @@ public class StreamCallPlugin : Plugin() {
|
|
|
277
289
|
}
|
|
278
290
|
}
|
|
279
291
|
} else {
|
|
280
|
-
|
|
292
|
+
Log.w("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - cid is null. Cannot process.")
|
|
281
293
|
}
|
|
282
294
|
}
|
|
283
295
|
} else if (action === "io.getstream.video.android.action.ACCEPT_CALL") {
|
|
284
|
-
|
|
296
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: Matched ACCEPT_CALL action")
|
|
285
297
|
val cid = intent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
|
|
286
|
-
|
|
298
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: ACCEPT_CALL - Extracted cid: $cid")
|
|
287
299
|
if (cid != null) {
|
|
288
|
-
|
|
300
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: ACCEPT_CALL - Accepting call with cid: $cid")
|
|
289
301
|
val call = streamVideoClient?.call(id = cid.id, type = cid.type)
|
|
290
302
|
if (call != null) {
|
|
291
303
|
// Log the full stack trace to see exactly where this is called from
|
|
292
304
|
val stackTrace = Thread.currentThread().stackTrace
|
|
293
|
-
|
|
305
|
+
Log.d("StreamCallPlugin", "internalAcceptCall STACK TRACE:")
|
|
294
306
|
stackTrace.forEachIndexed { index, element ->
|
|
295
|
-
|
|
307
|
+
Log.d("StreamCallPlugin", " [$index] ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
|
|
296
308
|
}
|
|
297
309
|
kotlinx.coroutines.GlobalScope.launch {
|
|
298
|
-
|
|
310
|
+
val isAudioOnly = getIsAudioOnly(call)
|
|
311
|
+
this@StreamCallPlugin.callIsAudioOnly = isAudioOnly
|
|
312
|
+
internalAcceptCall(call, requestPermissionsAfter = !checkPermissions(isAudioOnly))
|
|
299
313
|
}
|
|
300
314
|
bringAppToForeground()
|
|
301
315
|
} else {
|
|
302
|
-
|
|
316
|
+
Log.e("StreamCallPlugin", "handleOnNewIntent: ACCEPT_CALL - Call object is null for cid: $cid")
|
|
303
317
|
}
|
|
304
318
|
}
|
|
305
319
|
}
|
|
306
320
|
// Log the intent information
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
321
|
+
Log.d("StreamCallPlugin", "New Intent - Action: $action")
|
|
322
|
+
Log.d("StreamCallPlugin", "New Intent - Data: $data")
|
|
323
|
+
Log.d("StreamCallPlugin", "New Intent - Extras: $extras")
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
@OptIn(DelicateCoroutinesApi::class)
|
|
313
327
|
private fun declineCall(call: Call) {
|
|
314
|
-
|
|
328
|
+
Log.d("StreamCallPlugin", "declineCall called for call: ${call.id}")
|
|
315
329
|
kotlinx.coroutines.GlobalScope.launch {
|
|
316
330
|
try {
|
|
317
331
|
call.reject()
|
|
@@ -322,7 +336,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
322
336
|
|
|
323
337
|
hideIncomingCall()
|
|
324
338
|
} catch (e: Exception) {
|
|
325
|
-
|
|
339
|
+
Log.e("StreamCallPlugin", "Error declining call: ${e.message}")
|
|
326
340
|
}
|
|
327
341
|
}
|
|
328
342
|
}
|
|
@@ -333,18 +347,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
333
347
|
}
|
|
334
348
|
}
|
|
335
349
|
|
|
336
|
-
private fun showBarrier() {
|
|
337
|
-
activity?.runOnUiThread {
|
|
338
|
-
barrierView?.isVisible = true
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private fun hideBarrier() {
|
|
343
|
-
activity?.runOnUiThread {
|
|
344
|
-
barrierView?.isVisible = false
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
350
|
@OptIn(InternalStreamVideoApi::class)
|
|
349
351
|
private fun setupViews() {
|
|
350
352
|
val context = context
|
|
@@ -356,7 +358,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
356
358
|
if (rootParent != null && indexInRoot >= 0) {
|
|
357
359
|
rootParent.removeViewAt(indexInRoot)
|
|
358
360
|
touchInterceptWrapper = TouchInterceptWrapper(originalParent).apply {
|
|
359
|
-
setBackgroundColor(
|
|
361
|
+
setBackgroundColor(Color.TRANSPARENT)
|
|
360
362
|
}
|
|
361
363
|
rootParent.addView(touchInterceptWrapper, indexInRoot)
|
|
362
364
|
}
|
|
@@ -386,7 +388,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
386
388
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
387
389
|
ViewGroup.LayoutParams.MATCH_PARENT
|
|
388
390
|
)
|
|
389
|
-
setBackgroundColor(
|
|
391
|
+
setBackgroundColor("#1a242c".toColorInt())
|
|
390
392
|
}
|
|
391
393
|
parent.addView(barrierView, parent.indexOfChild(bridge?.webView) + 1) // Add above WebView
|
|
392
394
|
}
|
|
@@ -400,17 +402,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
400
402
|
VideoTheme {
|
|
401
403
|
val activeCall = call ?: streamVideoClient?.state?.activeCall?.collectAsState()?.value
|
|
402
404
|
if (activeCall != null) {
|
|
403
|
-
val participants by activeCall.state.participants.collectAsStateWithLifecycle()
|
|
404
|
-
val sortedParticipants by activeCall.state.sortedParticipants.collectAsStateWithLifecycle(emptyList())
|
|
405
|
-
val callParticipants by remember(participants) {
|
|
406
|
-
derivedStateOf {
|
|
407
|
-
if (sortedParticipants.size > 6) {
|
|
408
|
-
sortedParticipants
|
|
409
|
-
} else {
|
|
410
|
-
participants
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
405
|
|
|
415
406
|
val currentLocal by activeCall.state.me.collectAsStateWithLifecycle()
|
|
416
407
|
|
|
@@ -489,7 +480,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
489
480
|
SecureUserRepository.getInstance(context).save(credentials)
|
|
490
481
|
|
|
491
482
|
// Initialize Stream Video with new credentials
|
|
492
|
-
if (!hadSavedCredentials || (savedCredentials
|
|
483
|
+
if (!hadSavedCredentials || (savedCredentials.user.id != userId)) {
|
|
493
484
|
initializeStreamVideo()
|
|
494
485
|
}
|
|
495
486
|
|
|
@@ -532,10 +523,10 @@ public class StreamCallPlugin : Plugin() {
|
|
|
532
523
|
}
|
|
533
524
|
|
|
534
525
|
@OptIn(DelicateCoroutinesApi::class)
|
|
535
|
-
|
|
536
|
-
|
|
526
|
+
fun initializeStreamVideo(passedContext: Context? = null, passedApplication: Application? = null) {
|
|
527
|
+
Log.d("StreamCallPlugin", "initializeStreamVideo called")
|
|
537
528
|
if (state == State.INITIALIZING) {
|
|
538
|
-
|
|
529
|
+
Log.v("StreamCallPlugin", "Returning, already in the process of initializing")
|
|
539
530
|
return
|
|
540
531
|
}
|
|
541
532
|
state = State.INITIALIZING
|
|
@@ -548,7 +539,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
548
539
|
// Try to get user credentials from repository
|
|
549
540
|
val savedCredentials = SecureUserRepository.getInstance(contextToUse).loadCurrentUser()
|
|
550
541
|
if (savedCredentials == null) {
|
|
551
|
-
|
|
542
|
+
Log.v("StreamCallPlugin", "Saved credentials are null")
|
|
552
543
|
state = State.NOT_INITIALIZED
|
|
553
544
|
return
|
|
554
545
|
}
|
|
@@ -556,14 +547,14 @@ public class StreamCallPlugin : Plugin() {
|
|
|
556
547
|
try {
|
|
557
548
|
// Check if we can reuse existing StreamVideo singleton client
|
|
558
549
|
if (StreamVideo.isInstalled) {
|
|
559
|
-
|
|
550
|
+
Log.v("StreamCallPlugin", "Found existing StreamVideo singleton client")
|
|
560
551
|
if (streamVideoClient == null) {
|
|
561
|
-
|
|
552
|
+
Log.v("StreamCallPlugin", "Plugin's streamVideoClient is null, reusing singleton and registering event handlers")
|
|
562
553
|
streamVideoClient = StreamVideo.instance()
|
|
563
554
|
// Register event handlers since streamVideoClient was null
|
|
564
555
|
registerEventHandlers()
|
|
565
556
|
} else {
|
|
566
|
-
|
|
557
|
+
Log.v("StreamCallPlugin", "Plugin already has streamVideoClient, skipping event handler registration")
|
|
567
558
|
}
|
|
568
559
|
state = State.INITIALIZED
|
|
569
560
|
initializationTime = System.currentTimeMillis()
|
|
@@ -571,11 +562,11 @@ public class StreamCallPlugin : Plugin() {
|
|
|
571
562
|
}
|
|
572
563
|
|
|
573
564
|
// If we reach here, we need to create a new client
|
|
574
|
-
|
|
565
|
+
Log.v("StreamCallPlugin", "No existing StreamVideo singleton client, creating new one")
|
|
575
566
|
|
|
576
567
|
// unsafe cast, add better handling
|
|
577
568
|
val application = contextToUse.applicationContext as Application
|
|
578
|
-
|
|
569
|
+
Log.d("StreamCallPlugin", "No existing StreamVideo singleton client, creating new one")
|
|
579
570
|
val notificationHandler = CustomNotificationHandler(
|
|
580
571
|
application = application,
|
|
581
572
|
endCall = { callId ->
|
|
@@ -583,13 +574,13 @@ public class StreamCallPlugin : Plugin() {
|
|
|
583
574
|
|
|
584
575
|
kotlinx.coroutines.GlobalScope.launch {
|
|
585
576
|
try {
|
|
586
|
-
|
|
577
|
+
Log.i(
|
|
587
578
|
"StreamCallPlugin",
|
|
588
579
|
"Attempt to endCallRaw, activeCall == null: ${activeCall == null}",
|
|
589
580
|
)
|
|
590
581
|
activeCall?.let { endCallRaw(it) }
|
|
591
582
|
} catch (e: Exception) {
|
|
592
|
-
|
|
583
|
+
Log.e(
|
|
593
584
|
"StreamCallPlugin",
|
|
594
585
|
"Error ending after missed call notif action",
|
|
595
586
|
e
|
|
@@ -603,12 +594,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
603
594
|
val now = System.currentTimeMillis()
|
|
604
595
|
val isWithinOneSecond = (now - contextCreatedAt) <= 1000L
|
|
605
596
|
|
|
606
|
-
|
|
597
|
+
Log.i(
|
|
607
598
|
"StreamCallPlugin",
|
|
608
599
|
"Time between context creation and activity created (incoming call notif): ${now - contextCreatedAt}"
|
|
609
600
|
)
|
|
610
601
|
if (isWithinOneSecond && !bootedToHandleCall) {
|
|
611
|
-
|
|
602
|
+
Log.i(
|
|
612
603
|
"StreamCallPlugin",
|
|
613
604
|
"Notification incomingCall received less than 1 second after the creation of streamVideoSDK. Booted FOR SURE in order to handle the notification"
|
|
614
605
|
)
|
|
@@ -644,7 +635,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
644
635
|
|
|
645
636
|
// don't do event handler registration when activity may be null
|
|
646
637
|
if (passedContext != null) {
|
|
647
|
-
|
|
638
|
+
Log.w("StreamCallPlugin", "Ignoring event listeners for initializeStreamVideo")
|
|
648
639
|
passedApplication?.let {
|
|
649
640
|
registerActivityEventListener(it)
|
|
650
641
|
}
|
|
@@ -655,7 +646,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
655
646
|
|
|
656
647
|
registerEventHandlers()
|
|
657
648
|
|
|
658
|
-
|
|
649
|
+
Log.v("StreamCallPlugin", "Initialization finished")
|
|
659
650
|
initializationTime = System.currentTimeMillis()
|
|
660
651
|
state = State.INITIALIZED
|
|
661
652
|
} catch (e: Exception) {
|
|
@@ -667,7 +658,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
667
658
|
private fun moveAllActivitiesToBackgroundOrKill(context: Context, allowKill: Boolean = false) {
|
|
668
659
|
try {
|
|
669
660
|
if (allowKill && bootedToHandleCall && savedActivity != null) {
|
|
670
|
-
|
|
661
|
+
Log.d("StreamCallPlugin", "App was booted to handle call and allowKill is true, killing app")
|
|
671
662
|
savedActivity?.let { act ->
|
|
672
663
|
try {
|
|
673
664
|
// Get the ActivityManager
|
|
@@ -687,7 +678,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
687
678
|
android.os.Process.killProcess(android.os.Process.myPid())
|
|
688
679
|
}, 100)
|
|
689
680
|
} catch (e: Exception) {
|
|
690
|
-
|
|
681
|
+
Log.e("StreamCallPlugin", "Error during aggressive cleanup", e)
|
|
691
682
|
// Fallback to direct process kill
|
|
692
683
|
android.os.Process.killProcess(android.os.Process.myPid())
|
|
693
684
|
}
|
|
@@ -695,19 +686,20 @@ public class StreamCallPlugin : Plugin() {
|
|
|
695
686
|
return
|
|
696
687
|
}
|
|
697
688
|
|
|
698
|
-
val intent =
|
|
699
|
-
addCategory(
|
|
689
|
+
val intent = Intent(Intent.ACTION_MAIN).apply {
|
|
690
|
+
addCategory(Intent.CATEGORY_HOME)
|
|
700
691
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
701
692
|
}
|
|
702
693
|
context.startActivity(intent)
|
|
703
|
-
|
|
694
|
+
Log.d("StreamCallPlugin", "Moving app to background using HOME intent")
|
|
704
695
|
} catch (e: Exception) {
|
|
705
|
-
|
|
696
|
+
Log.e("StreamCallPlugin", "Failed to move app to background", e)
|
|
706
697
|
}
|
|
707
698
|
}
|
|
708
699
|
|
|
709
700
|
@OptIn(DelicateCoroutinesApi::class)
|
|
710
701
|
private fun registerEventHandlers() {
|
|
702
|
+
Log.d("StreamCallPlugin", "registerEventHandlers called")
|
|
711
703
|
eventSubscription?.dispose()
|
|
712
704
|
activeCallStateJob?.cancel()
|
|
713
705
|
cameraStatusJob?.cancel()
|
|
@@ -715,7 +707,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
715
707
|
// Subscribe to call events
|
|
716
708
|
streamVideoClient?.let { client ->
|
|
717
709
|
eventSubscription = client.subscribe { event: VideoEvent ->
|
|
718
|
-
|
|
710
|
+
Log.v("StreamCallPlugin", "Received an event ${event.getEventType()} $event")
|
|
719
711
|
when (event) {
|
|
720
712
|
is CallRingEvent -> {
|
|
721
713
|
// Extract caller information from the ringing call
|
|
@@ -736,7 +728,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
736
728
|
"userId" to callerInfo.id,
|
|
737
729
|
"name" to (callerInfo.name ?: ""),
|
|
738
730
|
"imageURL" to (callerInfo.image ?: ""),
|
|
739
|
-
"role" to (callerInfo.role
|
|
731
|
+
"role" to (callerInfo.role)
|
|
740
732
|
)
|
|
741
733
|
updateCallStatusAndNotify(event.callCid, "ringing", null, null, null, caller)
|
|
742
734
|
} else {
|
|
@@ -746,7 +738,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
746
738
|
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
747
739
|
}
|
|
748
740
|
} catch (e: Exception) {
|
|
749
|
-
|
|
741
|
+
Log.e("StreamCallPlugin", "Error getting caller info for ringing event", e)
|
|
750
742
|
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
751
743
|
}
|
|
752
744
|
}
|
|
@@ -754,9 +746,9 @@ public class StreamCallPlugin : Plugin() {
|
|
|
754
746
|
// Handle CallCreatedEvent differently - only log it but don't try to access members yet
|
|
755
747
|
is CallCreatedEvent -> {
|
|
756
748
|
val callCid = event.callCid
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
749
|
+
Log.d("StreamCallPlugin", "CallCreatedEvent: Received for $callCid")
|
|
750
|
+
Log.d("StreamCallPlugin", "CallCreatedEvent: All members from event: ${event.members.joinToString { it.user.id + " (role: " + it.user.role + ")" }}")
|
|
751
|
+
Log.d("StreamCallPlugin", "CallCreatedEvent: Self user ID from SDK: ${this@StreamCallPlugin.streamVideoClient?.userId}")
|
|
760
752
|
|
|
761
753
|
// Only send "created" event for outgoing calls (calls created by current user)
|
|
762
754
|
// For incoming calls, we'll only send "ringing" event in CallRingEvent handler
|
|
@@ -771,21 +763,21 @@ public class StreamCallPlugin : Plugin() {
|
|
|
771
763
|
val createdBy = callInfo?.getOrNull()?.call?.createdBy
|
|
772
764
|
val currentUserId = streamVideoClient?.userId
|
|
773
765
|
|
|
774
|
-
|
|
766
|
+
Log.d("StreamCallPlugin", "CallCreatedEvent: Call created by: ${createdBy?.id}, Current user: $currentUserId")
|
|
775
767
|
|
|
776
768
|
// Only notify for outgoing calls (where current user is the creator)
|
|
777
769
|
if (createdBy?.id == currentUserId) {
|
|
778
|
-
|
|
770
|
+
Log.d("StreamCallPlugin", "CallCreatedEvent: This is an outgoing call, sending created event")
|
|
779
771
|
|
|
780
772
|
val callParticipants = event.members.filter {
|
|
781
773
|
val selfId = this@StreamCallPlugin.streamVideoClient?.userId
|
|
782
774
|
val memberId = it.user.id
|
|
783
775
|
val isSelf = memberId == selfId
|
|
784
|
-
|
|
776
|
+
Log.d("StreamCallPlugin", "CallCreatedEvent: Filtering member $memberId. Self ID: $selfId. Is self: $isSelf")
|
|
785
777
|
!isSelf
|
|
786
778
|
}.map { it.user.id }
|
|
787
779
|
|
|
788
|
-
|
|
780
|
+
Log.d("StreamCallPlugin", "Call created for $callCid with ${callParticipants.size} remote participants: ${callParticipants.joinToString()}.")
|
|
789
781
|
|
|
790
782
|
// Start tracking this call now that we have the member list
|
|
791
783
|
startCallTimeoutMonitor(callCid, callParticipants)
|
|
@@ -796,19 +788,19 @@ public class StreamCallPlugin : Plugin() {
|
|
|
796
788
|
"userId" to member.user.id,
|
|
797
789
|
"name" to (member.user.name ?: ""),
|
|
798
790
|
"imageURL" to (member.user.image ?: ""),
|
|
799
|
-
"role" to (member.user.role
|
|
791
|
+
"role" to (member.user.role)
|
|
800
792
|
)
|
|
801
793
|
}
|
|
802
794
|
|
|
803
795
|
updateCallStatusAndNotify(callCid, "created", null, null, allMembers)
|
|
804
796
|
} else {
|
|
805
|
-
|
|
797
|
+
Log.d("StreamCallPlugin", "CallCreatedEvent: This is an incoming call (created by ${createdBy?.id}), not sending created event")
|
|
806
798
|
}
|
|
807
799
|
} else {
|
|
808
|
-
|
|
800
|
+
Log.w("StreamCallPlugin", "CallCreatedEvent: Invalid call CID format: $callCid")
|
|
809
801
|
}
|
|
810
802
|
} catch (e: Exception) {
|
|
811
|
-
|
|
803
|
+
Log.e("StreamCallPlugin", "Error processing CallCreatedEvent", e)
|
|
812
804
|
}
|
|
813
805
|
}
|
|
814
806
|
}
|
|
@@ -844,7 +836,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
844
836
|
|
|
845
837
|
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
846
838
|
if (keyguardManager.isKeyguardLocked) {
|
|
847
|
-
|
|
839
|
+
Log.d("StreamCallPlugin", "Stop ringing and move to background")
|
|
848
840
|
moveAllActivitiesToBackgroundOrKill(context)
|
|
849
841
|
}
|
|
850
842
|
|
|
@@ -863,7 +855,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
863
855
|
callState.participantResponses[userId] = "accepted"
|
|
864
856
|
|
|
865
857
|
// Since someone accepted, cancel the timeout timer
|
|
866
|
-
|
|
858
|
+
Log.d("StreamCallPlugin", "Call accepted by $userId, canceling timeout timer for $callCid")
|
|
867
859
|
callState.timer?.removeCallbacksAndMessages(null)
|
|
868
860
|
callState.timer = null
|
|
869
861
|
}
|
|
@@ -873,7 +865,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
873
865
|
|
|
874
866
|
is CallEndedEvent -> {
|
|
875
867
|
runOnMainThread {
|
|
876
|
-
|
|
868
|
+
Log.d("StreamCallPlugin", "Setting overlay invisible due to CallEndedEvent for call ${event.callCid}")
|
|
877
869
|
// Clean up call resources
|
|
878
870
|
val callCid = event.callCid
|
|
879
871
|
cleanupCall(callCid)
|
|
@@ -883,7 +875,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
883
875
|
|
|
884
876
|
is CallSessionEndedEvent -> {
|
|
885
877
|
runOnMainThread {
|
|
886
|
-
|
|
878
|
+
Log.d("StreamCallPlugin", "Setting overlay invisible due to CallSessionEndedEvent for call ${event.callCid}. Test session: ${event.call.session?.endedAt}")
|
|
887
879
|
// Clean up call resources
|
|
888
880
|
val callCid = event.callCid
|
|
889
881
|
cleanupCall(callCid)
|
|
@@ -907,23 +899,23 @@ public class StreamCallPlugin : Plugin() {
|
|
|
907
899
|
}
|
|
908
900
|
}
|
|
909
901
|
|
|
910
|
-
|
|
902
|
+
Log.d("StreamCallPlugin", "CallSessionParticipantLeftEvent: Received for call $callId. Active call: ${activeCall?.cid}")
|
|
911
903
|
|
|
912
904
|
|
|
913
905
|
if (activeCall != null && activeCall.cid == callId) {
|
|
914
906
|
val connectionState = activeCall.state.connection.value
|
|
915
907
|
if (connectionState != RealtimeConnection.Disconnected) {
|
|
916
908
|
val total = activeCall.state.participantCounts.value?.total
|
|
917
|
-
|
|
909
|
+
Log.d("StreamCallPlugin", "CallSessionParticipantLeftEvent: Participant left, remaining: $total")
|
|
918
910
|
if (total != null && total <= 1) {
|
|
919
|
-
|
|
911
|
+
Log.d("StreamCallPlugin", "CallSessionParticipantLeftEvent: All remote participants have left call ${activeCall.cid}. Ending call.")
|
|
920
912
|
kotlinx.coroutines.GlobalScope.launch(Dispatchers.IO) {
|
|
921
913
|
endCallRaw(activeCall)
|
|
922
914
|
}
|
|
923
915
|
}
|
|
924
916
|
}
|
|
925
917
|
} else {
|
|
926
|
-
|
|
918
|
+
Log.d("StreamCallPlugin", "CallSessionParticipantLeftEvent: Conditions not met (activeCall null, or cid mismatch, or local user not joined). ActiveCall CID: ${activeCall?.cid}")
|
|
927
919
|
}
|
|
928
920
|
}
|
|
929
921
|
|
|
@@ -940,13 +932,13 @@ public class StreamCallPlugin : Plugin() {
|
|
|
940
932
|
// used so that it follows the same patterns as iOS
|
|
941
933
|
activeCallStateJob = kotlinx.coroutines.GlobalScope.launch {
|
|
942
934
|
client.state.activeCall.collect { call ->
|
|
943
|
-
|
|
944
|
-
|
|
935
|
+
Log.d("StreamCallPlugin", "Call State Update:")
|
|
936
|
+
Log.d("StreamCallPlugin", "- Call is null: ${call == null}")
|
|
945
937
|
|
|
946
938
|
call?.state?.let { state ->
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
939
|
+
Log.d("StreamCallPlugin", "- Session ID: ${state.session.value?.id}")
|
|
940
|
+
Log.d("StreamCallPlugin", "- All participants: ${state.participants}")
|
|
941
|
+
Log.d("StreamCallPlugin", "- Remote participants: ${state.remoteParticipants}")
|
|
950
942
|
|
|
951
943
|
// Notify that a call has started or state updated (e.g., participants changed but still active)
|
|
952
944
|
// The actual check for "last participant" is now handled by CallSessionParticipantLeftEvent
|
|
@@ -960,7 +952,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
960
952
|
// Listen to camera status changes
|
|
961
953
|
cameraStatusJob = kotlinx.coroutines.GlobalScope.launch {
|
|
962
954
|
call.camera.isEnabled.collect { isEnabled ->
|
|
963
|
-
|
|
955
|
+
Log.d("StreamCallPlugin", "Camera status changed for call ${call.id}: enabled=$isEnabled")
|
|
964
956
|
updateCallStatusAndNotify(call.cid, if (isEnabled) "camera_enabled" else "camera_disabled")
|
|
965
957
|
}
|
|
966
958
|
}
|
|
@@ -968,7 +960,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
968
960
|
// Listen to microphone status changes
|
|
969
961
|
microphoneStatusJob = kotlinx.coroutines.GlobalScope.launch {
|
|
970
962
|
call.microphone.isEnabled.collect { isEnabled ->
|
|
971
|
-
|
|
963
|
+
Log.d("StreamCallPlugin", "Microphone status changed for call ${call.id}: enabled=$isEnabled")
|
|
972
964
|
updateCallStatusAndNotify(call.cid, if (isEnabled) "microphone_enabled" else "microphone_disabled")
|
|
973
965
|
}
|
|
974
966
|
}
|
|
@@ -985,18 +977,18 @@ public class StreamCallPlugin : Plugin() {
|
|
|
985
977
|
}
|
|
986
978
|
|
|
987
979
|
private fun registerActivityEventListener(application: Application) {
|
|
988
|
-
|
|
980
|
+
Log.i("StreamCallPlugin", "Registering activity event listener")
|
|
989
981
|
application.registerActivityLifecycleCallbacks(object: ActivityLifecycleCallbacks() {
|
|
990
982
|
override fun onActivityCreated(activity: Activity, bunlde: Bundle?) {
|
|
991
|
-
|
|
983
|
+
Log.d("StreamCallPlugin", "onActivityCreated called")
|
|
992
984
|
savedContext?.let {
|
|
993
985
|
if (this@StreamCallPlugin.savedActivity != null && activity is BridgeActivity) {
|
|
994
|
-
|
|
995
|
-
this@StreamCallPlugin.savedActivity = activity
|
|
986
|
+
Log.d("StreamCallPlugin", "Activity created before, but got re-created. saving and returning")
|
|
987
|
+
this@StreamCallPlugin.savedActivity = activity
|
|
996
988
|
return
|
|
997
989
|
}
|
|
998
990
|
if (initializationTime == 0L) {
|
|
999
|
-
|
|
991
|
+
Log.w("StreamCallPlugin", "initializationTime is zero. Not continuing with onActivityCreated")
|
|
1000
992
|
return
|
|
1001
993
|
}
|
|
1002
994
|
|
|
@@ -1004,8 +996,8 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1004
996
|
val isLocked = keyguardManager.isKeyguardLocked
|
|
1005
997
|
|
|
1006
998
|
if (isLocked) {
|
|
1007
|
-
this@StreamCallPlugin.bootedToHandleCall = true
|
|
1008
|
-
|
|
999
|
+
this@StreamCallPlugin.bootedToHandleCall = true
|
|
1000
|
+
Log.d("StreamCallPlugin", "Detected that the app booted an activity while locked. We will kill after the call fails")
|
|
1009
1001
|
}
|
|
1010
1002
|
|
|
1011
1003
|
if (this@StreamCallPlugin.bridge == null && activity is BridgeActivity) {
|
|
@@ -1027,7 +1019,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1027
1019
|
this@StreamCallPlugin.savedActivityPaused = false
|
|
1028
1020
|
}
|
|
1029
1021
|
for (call in this@StreamCallPlugin.savedCallsToEndOnResume) {
|
|
1030
|
-
|
|
1022
|
+
Log.d("StreamCallPlugin", "Trying to end call with ID ${call.id} on resume")
|
|
1031
1023
|
transEndCallRaw(call)
|
|
1032
1024
|
}
|
|
1033
1025
|
super.onActivityResumed(activity)
|
|
@@ -1036,38 +1028,28 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1036
1028
|
}
|
|
1037
1029
|
|
|
1038
1030
|
@PluginMethod
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
val streamVideoCall = streamVideoClient?.state?.ringingCall?.value
|
|
1043
|
-
if (streamVideoCall == null) {
|
|
1044
|
-
call.reject("Ringing call is null")
|
|
1045
|
-
return
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
android.util.Log.d("StreamCallPlugin", "acceptCall: Accepting call immediately, will handle permissions after")
|
|
1049
|
-
|
|
1050
|
-
// Accept call immediately regardless of permissions - time is critical!
|
|
1031
|
+
fun acceptCall(call: PluginCall) {
|
|
1032
|
+
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1033
|
+
if (ringingCall != null) {
|
|
1051
1034
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1052
1035
|
try {
|
|
1053
|
-
|
|
1036
|
+
val isAudioOnly = getIsAudioOnly(ringingCall)
|
|
1037
|
+
internalAcceptCall(ringingCall, requestPermissionsAfter = !checkPermissions(isAudioOnly))
|
|
1054
1038
|
call.resolve(JSObject().apply {
|
|
1055
1039
|
put("success", true)
|
|
1056
1040
|
})
|
|
1057
1041
|
} catch (e: Exception) {
|
|
1058
|
-
android.util.Log.e("StreamCallPlugin", "Error accepting call", e)
|
|
1059
1042
|
call.reject("Failed to accept call: ${e.message}")
|
|
1060
1043
|
}
|
|
1061
1044
|
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
call.reject("Cannot acceptCall")
|
|
1045
|
+
} else {
|
|
1046
|
+
call.reject("No ringing call")
|
|
1065
1047
|
}
|
|
1066
1048
|
}
|
|
1067
1049
|
|
|
1068
1050
|
@PluginMethod
|
|
1069
|
-
|
|
1070
|
-
|
|
1051
|
+
fun rejectCall(call: PluginCall) {
|
|
1052
|
+
Log.d("StreamCallPlugin", "rejectCall called")
|
|
1071
1053
|
try {
|
|
1072
1054
|
val streamVideoCall = streamVideoClient?.state?.ringingCall?.value
|
|
1073
1055
|
if (streamVideoCall == null) {
|
|
@@ -1078,103 +1060,106 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1078
1060
|
declineCall(streamVideoCall)
|
|
1079
1061
|
}
|
|
1080
1062
|
} catch (t: Throwable) {
|
|
1081
|
-
|
|
1063
|
+
Log.d("StreamCallPlugin", "JS -> rejectCall fail", t)
|
|
1082
1064
|
call.reject("Cannot rejectCall")
|
|
1083
1065
|
}
|
|
1084
1066
|
}
|
|
1085
1067
|
|
|
1086
1068
|
@OptIn(DelicateCoroutinesApi::class, InternalStreamVideoApi::class)
|
|
1087
1069
|
internal fun internalAcceptCall(call: Call, requestPermissionsAfter: Boolean = false) {
|
|
1088
|
-
|
|
1070
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Entered for call: ${call.id}, requestPermissionsAfter: $requestPermissionsAfter")
|
|
1089
1071
|
|
|
1090
1072
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1091
1073
|
try {
|
|
1092
|
-
|
|
1074
|
+
val isAudioOnly = getIsAudioOnly(call)
|
|
1075
|
+
this@StreamCallPlugin.callIsAudioOnly = isAudioOnly
|
|
1076
|
+
|
|
1077
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Coroutine started for call ${call.id}")
|
|
1093
1078
|
|
|
1094
1079
|
// Hide incoming call view first
|
|
1095
1080
|
runOnMainThread {
|
|
1096
|
-
|
|
1081
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Hiding incoming call view for call ${call.id}")
|
|
1097
1082
|
// No dedicated incoming-call native view anymore; UI handled by web layer
|
|
1098
1083
|
}
|
|
1099
|
-
|
|
1084
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Incoming call view hidden for call ${call.id}")
|
|
1100
1085
|
|
|
1101
1086
|
// Accept and join call immediately - don't wait for permissions!
|
|
1102
|
-
|
|
1087
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Accepting call immediately for ${call.id}")
|
|
1103
1088
|
call.accept()
|
|
1104
|
-
|
|
1089
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: call.accept() completed for call ${call.id}")
|
|
1105
1090
|
call.join()
|
|
1106
|
-
|
|
1091
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: call.join() completed for call ${call.id}")
|
|
1107
1092
|
streamVideoClient?.state?.setActiveCall(call)
|
|
1108
|
-
|
|
1093
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: setActiveCall completed for call ${call.id}")
|
|
1109
1094
|
|
|
1110
1095
|
// Notify that call has started using helper
|
|
1111
1096
|
updateCallStatusAndNotify(call.id, "joined")
|
|
1112
|
-
|
|
1097
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: updateCallStatusAndNotify(joined) called for ${call.id}")
|
|
1113
1098
|
|
|
1114
1099
|
// Show overlay view with the active call and make webview transparent
|
|
1115
1100
|
runOnMainThread {
|
|
1116
|
-
|
|
1101
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Updating UI for active call ${call.id} - setting overlay visible.")
|
|
1117
1102
|
bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
|
|
1118
|
-
|
|
1103
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: WebView background set to transparent for call ${call.id}")
|
|
1119
1104
|
bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
|
|
1120
|
-
|
|
1121
|
-
|
|
1105
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: WebView brought to front for call ${call.id}")
|
|
1106
|
+
|
|
1122
1107
|
// Enable camera/microphone based on permissions
|
|
1123
|
-
val hasPermissions = checkPermissions()
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
call.microphone
|
|
1127
|
-
call.camera
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1108
|
+
val hasPermissions = checkPermissions(isAudioOnly)
|
|
1109
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Has permissions: $hasPermissions for call ${call.id}")
|
|
1110
|
+
|
|
1111
|
+
call.microphone.setEnabled(hasPermissions)
|
|
1112
|
+
call.camera.setEnabled(hasPermissions && !isAudioOnly)
|
|
1113
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Microphone and camera set to $hasPermissions for call ${call.id}")
|
|
1114
|
+
|
|
1115
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Setting CallContent with active call ${call.id}")
|
|
1131
1116
|
setOverlayContent(call)
|
|
1132
|
-
|
|
1117
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Content set for overlayView for call ${call.id}")
|
|
1133
1118
|
overlayView?.isVisible = true
|
|
1134
|
-
|
|
1119
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: OverlayView set to visible for call ${call.id}, isVisible: ${overlayView?.isVisible}")
|
|
1135
1120
|
|
|
1136
1121
|
// Ensure overlay is behind WebView by adjusting its position in the parent
|
|
1137
1122
|
val parent = overlayView?.parent as? ViewGroup
|
|
1138
1123
|
parent?.removeView(overlayView)
|
|
1139
1124
|
parent?.addView(overlayView, 0) // Add at index 0 to ensure it's behind other views
|
|
1140
|
-
|
|
1141
|
-
|
|
1125
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: OverlayView re-added to parent at index 0 for call ${call.id}")
|
|
1126
|
+
|
|
1142
1127
|
// Add a small delay to ensure UI refresh
|
|
1143
1128
|
mainHandler.postDelayed({
|
|
1144
|
-
|
|
1129
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Delayed UI check, overlay visible: ${overlayView?.isVisible} for call ${call.id}")
|
|
1145
1130
|
if (overlayView?.isVisible == true) {
|
|
1146
1131
|
overlayView?.invalidate()
|
|
1147
1132
|
overlayView?.requestLayout()
|
|
1148
|
-
|
|
1133
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: UI invalidated and layout requested for call ${call.id}")
|
|
1149
1134
|
// Force refresh with active call from client
|
|
1150
1135
|
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1151
1136
|
if (activeCall != null) {
|
|
1152
|
-
|
|
1137
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Force refreshing CallContent with active call ${activeCall.id}")
|
|
1153
1138
|
setOverlayContent(activeCall)
|
|
1154
|
-
|
|
1139
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Content force refreshed for call ${activeCall.id}")
|
|
1155
1140
|
} else {
|
|
1156
|
-
|
|
1141
|
+
Log.w("StreamCallPlugin", "internalAcceptCall: Active call is null during force refresh for call ${call.id}")
|
|
1157
1142
|
}
|
|
1158
1143
|
} else {
|
|
1159
|
-
|
|
1144
|
+
Log.w("StreamCallPlugin", "internalAcceptCall: overlayView not visible after delay for call ${call.id}")
|
|
1160
1145
|
}
|
|
1161
1146
|
}, 1000) // Increased delay to ensure all events are processed
|
|
1162
1147
|
}
|
|
1163
1148
|
|
|
1164
1149
|
// Request permissions after joining if needed
|
|
1165
1150
|
if (requestPermissionsAfter) {
|
|
1166
|
-
|
|
1151
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Requesting permissions after call acceptance for ${call.id}")
|
|
1167
1152
|
runOnMainThread {
|
|
1168
1153
|
// Store reference to the active call for enabling camera/mic later
|
|
1169
1154
|
pendingAcceptCall = call
|
|
1170
|
-
|
|
1155
|
+
Log.d("StreamCallPlugin", "internalAcceptCall: Set pendingAcceptCall to ${call.id}, resetting attempt count")
|
|
1171
1156
|
permissionAttemptCount = 0
|
|
1172
|
-
requestPermissions()
|
|
1157
|
+
requestPermissions(isAudioOnly)
|
|
1173
1158
|
}
|
|
1174
1159
|
}
|
|
1175
|
-
|
|
1160
|
+
|
|
1176
1161
|
} catch (e: Exception) {
|
|
1177
|
-
|
|
1162
|
+
Log.e("StreamCallPlugin", "internalAcceptCall: Error accepting call ${call.id}: ${e.message}", e)
|
|
1178
1163
|
runOnMainThread {
|
|
1179
1164
|
android.widget.Toast.makeText(
|
|
1180
1165
|
context,
|
|
@@ -1187,55 +1172,62 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1187
1172
|
}
|
|
1188
1173
|
|
|
1189
1174
|
// Function to check required permissions
|
|
1190
|
-
private fun checkPermissions(): Boolean {
|
|
1191
|
-
|
|
1175
|
+
private fun checkPermissions(isAudioOnly: Boolean = false): Boolean {
|
|
1176
|
+
Log.d("StreamCallPlugin", "checkPermissions: Entered, isAudioOnly: $isAudioOnly")
|
|
1192
1177
|
val audioPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO)
|
|
1193
|
-
|
|
1178
|
+
Log.d("StreamCallPlugin", "checkPermissions: RECORD_AUDIO permission status: $audioPermission (Granted=${PackageManager.PERMISSION_GRANTED})")
|
|
1179
|
+
|
|
1180
|
+
if (isAudioOnly) {
|
|
1181
|
+
val allGranted = audioPermission == PackageManager.PERMISSION_GRANTED
|
|
1182
|
+
Log.d("StreamCallPlugin", "checkPermissions: Audio only call, all permissions granted: $allGranted")
|
|
1183
|
+
return allGranted
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1194
1186
|
val cameraPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
|
1195
|
-
|
|
1187
|
+
Log.d("StreamCallPlugin", "checkPermissions: CAMERA permission status: $cameraPermission (Granted=${PackageManager.PERMISSION_GRANTED})")
|
|
1196
1188
|
val allGranted = audioPermission == PackageManager.PERMISSION_GRANTED && cameraPermission == PackageManager.PERMISSION_GRANTED
|
|
1197
|
-
|
|
1189
|
+
Log.d("StreamCallPlugin", "checkPermissions: All permissions granted: $allGranted")
|
|
1198
1190
|
return allGranted
|
|
1199
1191
|
}
|
|
1200
1192
|
|
|
1201
1193
|
// Override to handle permission results
|
|
1202
1194
|
override fun handleRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
|
1203
1195
|
super.handleRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
1204
|
-
|
|
1205
|
-
|
|
1196
|
+
Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Entered. RequestCode: $requestCode, Attempt: $permissionAttemptCount")
|
|
1197
|
+
Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Expected requestCode: 9001")
|
|
1206
1198
|
|
|
1207
1199
|
if (requestCode == 9001) {
|
|
1208
1200
|
val responseTime = System.currentTimeMillis() - permissionRequestStartTime
|
|
1209
|
-
|
|
1201
|
+
Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Response time: ${responseTime}ms")
|
|
1210
1202
|
|
|
1211
1203
|
logPermissionResults(permissions, grantResults)
|
|
1212
1204
|
|
|
1213
1205
|
if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
|
|
1214
|
-
|
|
1206
|
+
Log.i("StreamCallPlugin", "handleRequestPermissionsResult: All permissions GRANTED.")
|
|
1215
1207
|
// Reset attempt count on success
|
|
1216
1208
|
permissionAttemptCount = 0
|
|
1217
1209
|
handlePermissionGranted()
|
|
1218
1210
|
} else {
|
|
1219
|
-
|
|
1211
|
+
Log.e("StreamCallPlugin", "handleRequestPermissionsResult: Permissions DENIED. Attempt: $permissionAttemptCount")
|
|
1220
1212
|
handlePermissionDenied(responseTime)
|
|
1221
1213
|
}
|
|
1222
1214
|
} else {
|
|
1223
|
-
|
|
1215
|
+
Log.w("StreamCallPlugin", "handleRequestPermissionsResult: Received unknown requestCode: $requestCode")
|
|
1224
1216
|
}
|
|
1225
1217
|
}
|
|
1226
1218
|
|
|
1227
1219
|
private fun logPermissionResults(permissions: Array<out String>, grantResults: IntArray) {
|
|
1228
|
-
|
|
1220
|
+
Log.d("StreamCallPlugin", "logPermissionResults: Logging permission results:")
|
|
1229
1221
|
for (i in permissions.indices) {
|
|
1230
1222
|
val permission = permissions[i]
|
|
1231
1223
|
val grantResult = if (grantResults.size > i) grantResults[i] else -999 // -999 for safety if arrays mismatch
|
|
1232
1224
|
val resultString = if (grantResult == PackageManager.PERMISSION_GRANTED) "GRANTED" else "DENIED ($grantResult)"
|
|
1233
|
-
|
|
1225
|
+
Log.d("StreamCallPlugin", " Permission: $permission, Result: $resultString")
|
|
1234
1226
|
}
|
|
1235
1227
|
}
|
|
1236
1228
|
|
|
1237
1229
|
private fun handlePermissionGranted() {
|
|
1238
|
-
|
|
1230
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Processing granted permissions")
|
|
1239
1231
|
|
|
1240
1232
|
// Reset attempt count since permissions are now granted
|
|
1241
1233
|
permissionAttemptCount = 0
|
|
@@ -1243,13 +1235,14 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1243
1235
|
// Determine what type of pending operation we have
|
|
1244
1236
|
val hasOutgoingCall = pendingCall != null && pendingCallUserIds != null
|
|
1245
1237
|
val hasActiveCallNeedingPermissions = pendingAcceptCall != null
|
|
1238
|
+
val hasPendingSetCamera = pendingSetCameraCall != null
|
|
1246
1239
|
|
|
1247
|
-
|
|
1240
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: hasOutgoingCall=$hasOutgoingCall, hasActiveCallNeedingPermissions=$hasActiveCallNeedingPermissions, hasPendingSetCamera=$hasPendingSetCamera")
|
|
1248
1241
|
|
|
1249
1242
|
when {
|
|
1250
1243
|
hasOutgoingCall -> {
|
|
1251
1244
|
// Outgoing call creation was waiting for permissions
|
|
1252
|
-
|
|
1245
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Executing pending outgoing call with ${pendingCallUserIds?.size} users")
|
|
1253
1246
|
executePendingCall()
|
|
1254
1247
|
}
|
|
1255
1248
|
|
|
@@ -1258,17 +1251,17 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1258
1251
|
val callToHandle = pendingAcceptCall!!
|
|
1259
1252
|
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1260
1253
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1254
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Processing call ${callToHandle.id}")
|
|
1255
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Active call in state: ${activeCall?.id}")
|
|
1263
1256
|
|
|
1264
1257
|
if (activeCall != null && activeCall.id == callToHandle.id) {
|
|
1265
1258
|
// Call is already active - enable camera/microphone
|
|
1266
|
-
|
|
1259
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Enabling camera/microphone for active call ${callToHandle.id}")
|
|
1267
1260
|
runOnMainThread {
|
|
1268
1261
|
try {
|
|
1269
|
-
callToHandle.microphone
|
|
1270
|
-
callToHandle.camera
|
|
1271
|
-
|
|
1262
|
+
callToHandle.microphone.setEnabled(true)
|
|
1263
|
+
callToHandle.camera.setEnabled(!this.callIsAudioOnly)
|
|
1264
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Camera and microphone enabled for call ${callToHandle.id}")
|
|
1272
1265
|
|
|
1273
1266
|
// Show success message
|
|
1274
1267
|
android.widget.Toast.makeText(
|
|
@@ -1277,13 +1270,13 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1277
1270
|
android.widget.Toast.LENGTH_SHORT
|
|
1278
1271
|
).show()
|
|
1279
1272
|
} catch (e: Exception) {
|
|
1280
|
-
|
|
1273
|
+
Log.e("StreamCallPlugin", "Error enabling camera/microphone", e)
|
|
1281
1274
|
}
|
|
1282
1275
|
clearPendingCall()
|
|
1283
1276
|
}
|
|
1284
1277
|
} else if (pendingCall != null) {
|
|
1285
1278
|
// Call not active yet - accept it (old flow, shouldn't happen with new flow)
|
|
1286
|
-
|
|
1279
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Accepting pending incoming call ${callToHandle.id}")
|
|
1287
1280
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1288
1281
|
try {
|
|
1289
1282
|
internalAcceptCall(callToHandle)
|
|
@@ -1291,7 +1284,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1291
1284
|
put("success", true)
|
|
1292
1285
|
})
|
|
1293
1286
|
} catch (e: Exception) {
|
|
1294
|
-
|
|
1287
|
+
Log.e("StreamCallPlugin", "Error accepting call after permission grant", e)
|
|
1295
1288
|
pendingCall?.reject("Failed to accept call: ${e.message}")
|
|
1296
1289
|
} finally {
|
|
1297
1290
|
clearPendingCall()
|
|
@@ -1299,12 +1292,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1299
1292
|
}
|
|
1300
1293
|
} else {
|
|
1301
1294
|
// Just enable camera/mic for the stored call even if not currently active
|
|
1302
|
-
|
|
1295
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Enabling camera/microphone for stored call ${callToHandle.id}")
|
|
1303
1296
|
runOnMainThread {
|
|
1304
1297
|
try {
|
|
1305
|
-
callToHandle.microphone
|
|
1306
|
-
callToHandle.camera
|
|
1307
|
-
|
|
1298
|
+
callToHandle.microphone.setEnabled(true)
|
|
1299
|
+
callToHandle.camera.setEnabled(!this.callIsAudioOnly)
|
|
1300
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Camera and microphone enabled for stored call ${callToHandle.id}")
|
|
1308
1301
|
|
|
1309
1302
|
android.widget.Toast.makeText(
|
|
1310
1303
|
context,
|
|
@@ -1312,23 +1305,49 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1312
1305
|
android.widget.Toast.LENGTH_SHORT
|
|
1313
1306
|
).show()
|
|
1314
1307
|
} catch (e: Exception) {
|
|
1315
|
-
|
|
1308
|
+
Log.e("StreamCallPlugin", "Error enabling camera/microphone for stored call", e)
|
|
1316
1309
|
}
|
|
1317
1310
|
clearPendingCall()
|
|
1318
1311
|
}
|
|
1319
1312
|
}
|
|
1320
1313
|
}
|
|
1321
1314
|
|
|
1315
|
+
hasPendingSetCamera -> {
|
|
1316
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Handling pending setCameraEnabled call.")
|
|
1317
|
+
val callToHandle = pendingSetCameraCall!!
|
|
1318
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1319
|
+
|
|
1320
|
+
if (activeCall != null) {
|
|
1321
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1322
|
+
try {
|
|
1323
|
+
activeCall.camera.setEnabled(true)
|
|
1324
|
+
this@StreamCallPlugin.callIsAudioOnly = false
|
|
1325
|
+
callToHandle.resolve(JSObject().apply {
|
|
1326
|
+
put("success", true)
|
|
1327
|
+
})
|
|
1328
|
+
} catch (e: Exception) {
|
|
1329
|
+
Log.e("StreamCallPlugin", "Error enabling camera after permission grant", e)
|
|
1330
|
+
callToHandle.reject("Failed to enable camera after permission grant: ${e.message}")
|
|
1331
|
+
} finally {
|
|
1332
|
+
clearPendingCall()
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
} else {
|
|
1336
|
+
callToHandle.reject("No active call found to enable camera.")
|
|
1337
|
+
clearPendingCall()
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1322
1341
|
pendingCall != null -> {
|
|
1323
1342
|
// We have a pending call but unclear what type - fallback handling
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1343
|
+
Log.w("StreamCallPlugin", "handlePermissionGranted: Have pendingCall but unclear operation type")
|
|
1344
|
+
Log.w("StreamCallPlugin", " - pendingCallUserIds: ${pendingCallUserIds != null}")
|
|
1345
|
+
Log.w("StreamCallPlugin", " - pendingAcceptCall: ${pendingAcceptCall != null}")
|
|
1327
1346
|
|
|
1328
1347
|
// Try fallback to current ringing call for acceptance
|
|
1329
1348
|
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1330
1349
|
if (ringingCall != null) {
|
|
1331
|
-
|
|
1350
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Fallback - accepting current ringing call ${ringingCall.id}")
|
|
1332
1351
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1333
1352
|
try {
|
|
1334
1353
|
internalAcceptCall(ringingCall)
|
|
@@ -1336,43 +1355,43 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1336
1355
|
put("success", true)
|
|
1337
1356
|
})
|
|
1338
1357
|
} catch (e: Exception) {
|
|
1339
|
-
|
|
1358
|
+
Log.e("StreamCallPlugin", "Error accepting fallback call after permission grant", e)
|
|
1340
1359
|
pendingCall?.reject("Failed to accept call: ${e.message}")
|
|
1341
1360
|
} finally {
|
|
1342
1361
|
clearPendingCall()
|
|
1343
1362
|
}
|
|
1344
1363
|
}
|
|
1345
1364
|
} else {
|
|
1346
|
-
|
|
1365
|
+
Log.w("StreamCallPlugin", "handlePermissionGranted: No ringing call found for fallback")
|
|
1347
1366
|
pendingCall?.reject("Unable to determine pending operation")
|
|
1348
1367
|
clearPendingCall()
|
|
1349
1368
|
}
|
|
1350
1369
|
}
|
|
1351
1370
|
|
|
1352
1371
|
else -> {
|
|
1353
|
-
|
|
1372
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: No pending operations to handle")
|
|
1354
1373
|
}
|
|
1355
1374
|
}
|
|
1356
1375
|
}
|
|
1357
1376
|
|
|
1358
1377
|
private fun handlePermissionDenied(responseTime: Long) {
|
|
1359
|
-
|
|
1378
|
+
Log.d("StreamCallPlugin", "handlePermissionDenied: Response time: ${responseTime}ms, Attempt: $permissionAttemptCount")
|
|
1360
1379
|
|
|
1361
1380
|
// Check if the response was instant (< 500ms) indicating "don't ask again"
|
|
1362
1381
|
val instantDenial = responseTime < 500
|
|
1363
|
-
|
|
1382
|
+
Log.d("StreamCallPlugin", "handlePermissionDenied: Instant denial detected: $instantDenial")
|
|
1364
1383
|
|
|
1365
1384
|
if (instantDenial) {
|
|
1366
1385
|
// If it's an instant denial (don't ask again), go straight to settings dialog
|
|
1367
|
-
|
|
1386
|
+
Log.d("StreamCallPlugin", "handlePermissionDenied: Instant denial, showing settings dialog")
|
|
1368
1387
|
showPermissionSettingsDialog()
|
|
1369
1388
|
} else if (permissionAttemptCount < 2) {
|
|
1370
1389
|
// Try asking again immediately if this is the first denial
|
|
1371
|
-
|
|
1372
|
-
requestPermissions() // This will increment the attempt count
|
|
1390
|
+
Log.d("StreamCallPlugin", "handlePermissionDenied: First denial (attempt $permissionAttemptCount), asking again immediately")
|
|
1391
|
+
requestPermissions(this.callIsAudioOnly) // This will increment the attempt count
|
|
1373
1392
|
} else {
|
|
1374
1393
|
// Second denial - show settings dialog (final ask)
|
|
1375
|
-
|
|
1394
|
+
Log.d("StreamCallPlugin", "handlePermissionDenied: Second denial (attempt $permissionAttemptCount), showing settings dialog (final ask)")
|
|
1376
1395
|
showPermissionSettingsDialog()
|
|
1377
1396
|
}
|
|
1378
1397
|
}
|
|
@@ -1386,15 +1405,15 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1386
1405
|
val custom = pendingCustomObject
|
|
1387
1406
|
|
|
1388
1407
|
if (call != null && userIds != null && callType != null && shouldRing != null) {
|
|
1389
|
-
|
|
1408
|
+
Log.d("StreamCallPlugin", "executePendingCall: Executing call with ${userIds.size} users")
|
|
1390
1409
|
|
|
1391
1410
|
// Clear pending call data
|
|
1392
1411
|
clearPendingCall()
|
|
1393
1412
|
|
|
1394
1413
|
// Execute the call creation logic
|
|
1395
|
-
createAndStartCall(call, userIds, callType, shouldRing, team, custom)
|
|
1414
|
+
createAndStartCall(call, userIds, callType, shouldRing, team, custom, this.callIsAudioOnly)
|
|
1396
1415
|
} else {
|
|
1397
|
-
|
|
1416
|
+
Log.w("StreamCallPlugin", "executePendingCall: Missing pending call data")
|
|
1398
1417
|
call?.reject("Internal error: missing call parameters")
|
|
1399
1418
|
clearPendingCall()
|
|
1400
1419
|
}
|
|
@@ -1407,14 +1426,14 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1407
1426
|
pendingCallShouldRing = null
|
|
1408
1427
|
pendingCallTeam = null
|
|
1409
1428
|
pendingAcceptCall = null
|
|
1410
|
-
|
|
1429
|
+
pendingSetCameraCall = null
|
|
1411
1430
|
permissionAttemptCount = 0 // Reset attempt count when clearing
|
|
1412
1431
|
}
|
|
1413
1432
|
|
|
1414
1433
|
|
|
1415
1434
|
|
|
1416
1435
|
@OptIn(DelicateCoroutinesApi::class, InternalStreamVideoApi::class)
|
|
1417
|
-
private fun createAndStartCall(call: PluginCall, userIds: List<String>, callType: String, shouldRing: Boolean, team: String?, custom: JSObject
|
|
1436
|
+
private fun createAndStartCall(call: PluginCall, userIds: List<String>, callType: String, shouldRing: Boolean, team: String?, custom: JSObject?, isAudioOnly: Boolean) {
|
|
1418
1437
|
val selfUserId = streamVideoClient?.userId
|
|
1419
1438
|
if (selfUserId == null) {
|
|
1420
1439
|
call.reject("No self-user id found. Are you not logged in?")
|
|
@@ -1433,24 +1452,25 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1433
1452
|
// instead, which contains the actual participant list
|
|
1434
1453
|
|
|
1435
1454
|
|
|
1436
|
-
|
|
1455
|
+
Log.d("StreamCallPlugin", "Creating call with members...")
|
|
1437
1456
|
// Create the call with all members
|
|
1438
1457
|
val createResult = streamCall?.create(
|
|
1439
1458
|
memberIds = userIds + selfUserId,
|
|
1440
1459
|
custom = custom?.toMap() ?: emptyMap(),
|
|
1441
1460
|
ring = shouldRing,
|
|
1442
1461
|
team = team,
|
|
1462
|
+
video = !isAudioOnly
|
|
1443
1463
|
)
|
|
1444
1464
|
|
|
1445
1465
|
if (createResult?.isFailure == true) {
|
|
1446
1466
|
throw (createResult.errorOrNull() ?: RuntimeException("Unknown error creating call")) as Throwable
|
|
1447
1467
|
}
|
|
1448
1468
|
|
|
1449
|
-
|
|
1469
|
+
Log.d("StreamCallPlugin", "Setting overlay visible for outgoing call $callId")
|
|
1450
1470
|
// Show overlay view
|
|
1451
1471
|
activity?.runOnUiThread {
|
|
1452
1472
|
streamCall?.microphone?.setEnabled(true)
|
|
1453
|
-
streamCall?.camera?.setEnabled(
|
|
1473
|
+
streamCall?.camera?.setEnabled(!isAudioOnly)
|
|
1454
1474
|
|
|
1455
1475
|
bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
|
|
1456
1476
|
bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
|
|
@@ -1467,28 +1487,34 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1467
1487
|
put("success", true)
|
|
1468
1488
|
})
|
|
1469
1489
|
} catch (e: Exception) {
|
|
1470
|
-
|
|
1490
|
+
Log.e("StreamCallPlugin", "Error making call: ${e.message}")
|
|
1471
1491
|
call.reject("Failed to make call: ${e.message}")
|
|
1472
1492
|
}
|
|
1473
1493
|
}
|
|
1474
1494
|
}
|
|
1475
1495
|
|
|
1476
1496
|
// Function to request required permissions
|
|
1477
|
-
private fun requestPermissions() {
|
|
1497
|
+
private fun requestPermissions(isAudioOnly: Boolean) {
|
|
1478
1498
|
permissionAttemptCount++
|
|
1479
|
-
|
|
1499
|
+
Log.d("StreamCallPlugin", "requestPermissions: Attempt #$permissionAttemptCount - Requesting permissions. isAudioOnly: $isAudioOnly")
|
|
1480
1500
|
|
|
1481
1501
|
// Record timing for instant denial detection
|
|
1482
1502
|
permissionRequestStartTime = System.currentTimeMillis()
|
|
1483
|
-
|
|
1503
|
+
Log.d("StreamCallPlugin", "requestPermissions: Starting permission request at $permissionRequestStartTime")
|
|
1484
1504
|
|
|
1505
|
+
val permissionsToRequest = if (isAudioOnly) {
|
|
1506
|
+
arrayOf(Manifest.permission.RECORD_AUDIO)
|
|
1507
|
+
} else {
|
|
1508
|
+
arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1485
1511
|
ActivityCompat.requestPermissions(
|
|
1486
1512
|
activity,
|
|
1487
|
-
|
|
1513
|
+
permissionsToRequest,
|
|
1488
1514
|
9001 // Use high request code to avoid Capacitor conflicts
|
|
1489
1515
|
)
|
|
1490
1516
|
|
|
1491
|
-
|
|
1517
|
+
Log.d("StreamCallPlugin", "requestPermissions: Permission request initiated with code 9001 for permissions: ${permissionsToRequest.joinToString()}")
|
|
1492
1518
|
}
|
|
1493
1519
|
|
|
1494
1520
|
private fun showPermissionSettingsDialog() {
|
|
@@ -1502,19 +1528,19 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1502
1528
|
if (hasActiveCall) {
|
|
1503
1529
|
builder.setMessage("Your call is active but camera and microphone are disabled.\n\nWould you like to open Settings to enable video and audio?")
|
|
1504
1530
|
builder.setNegativeButton("Continue without") { _, _ ->
|
|
1505
|
-
|
|
1531
|
+
Log.d("StreamCallPlugin", "User chose to continue call without permissions")
|
|
1506
1532
|
showPermissionRequiredMessage()
|
|
1507
1533
|
}
|
|
1508
1534
|
} else {
|
|
1509
1535
|
builder.setMessage("To make video calls, this app needs Camera and Microphone permissions.\n\nWould you like to open Settings to enable them?")
|
|
1510
1536
|
builder.setNegativeButton("Cancel") { _, _ ->
|
|
1511
|
-
|
|
1537
|
+
Log.d("StreamCallPlugin", "User declined to grant permissions - final rejection")
|
|
1512
1538
|
showPermissionRequiredMessage()
|
|
1513
1539
|
}
|
|
1514
1540
|
}
|
|
1515
1541
|
|
|
1516
1542
|
builder.setPositiveButton("Open Settings") { _, _ ->
|
|
1517
|
-
|
|
1543
|
+
Log.d("StreamCallPlugin", "User chose to open app settings")
|
|
1518
1544
|
openAppSettings()
|
|
1519
1545
|
// Don't reject the call yet - let them go to settings and come back
|
|
1520
1546
|
}
|
|
@@ -1548,31 +1574,38 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1548
1574
|
}
|
|
1549
1575
|
|
|
1550
1576
|
private fun handleFinalPermissionDenial() {
|
|
1551
|
-
|
|
1577
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Processing final permission denial")
|
|
1552
1578
|
|
|
1553
1579
|
val hasOutgoingCall = pendingCall != null && pendingCallUserIds != null
|
|
1554
1580
|
val hasIncomingCall = pendingCall != null && pendingAcceptCall != null
|
|
1581
|
+
val hasPendingSetCamera = pendingSetCameraCall != null
|
|
1555
1582
|
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1556
1583
|
|
|
1557
1584
|
when {
|
|
1558
1585
|
hasOutgoingCall -> {
|
|
1559
1586
|
// Outgoing call that couldn't be created due to permissions
|
|
1560
|
-
|
|
1587
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Rejecting outgoing call creation")
|
|
1561
1588
|
pendingCall?.reject("Permissions required for call. Please grant them.")
|
|
1562
1589
|
clearPendingCall()
|
|
1563
1590
|
}
|
|
1564
1591
|
|
|
1592
|
+
hasPendingSetCamera -> {
|
|
1593
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Rejecting pending setCameraEnabled call")
|
|
1594
|
+
pendingSetCameraCall?.reject("Camera permission is required to enable the camera.")
|
|
1595
|
+
clearPendingCall()
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1565
1598
|
hasIncomingCall && activeCall != null && activeCall.id == pendingAcceptCall?.id -> {
|
|
1566
1599
|
// Incoming call that's already active - DON'T end the call, just keep it without camera/mic
|
|
1567
|
-
|
|
1600
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Incoming call already active, keeping call without camera/mic")
|
|
1568
1601
|
|
|
1569
1602
|
// Ensure camera and microphone are disabled since no permissions
|
|
1570
1603
|
try {
|
|
1571
|
-
activeCall.microphone
|
|
1572
|
-
activeCall.camera
|
|
1573
|
-
|
|
1604
|
+
activeCall.microphone.setEnabled(false)
|
|
1605
|
+
activeCall.camera.setEnabled(false)
|
|
1606
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Disabled camera/microphone for call ${activeCall.id}")
|
|
1574
1607
|
} catch (e: Exception) {
|
|
1575
|
-
|
|
1608
|
+
Log.w("StreamCallPlugin", "handleFinalPermissionDenial: Error disabling camera/mic", e)
|
|
1576
1609
|
}
|
|
1577
1610
|
|
|
1578
1611
|
android.widget.Toast.makeText(
|
|
@@ -1591,13 +1624,13 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1591
1624
|
|
|
1592
1625
|
hasIncomingCall -> {
|
|
1593
1626
|
// Incoming call that wasn't accepted yet (old flow)
|
|
1594
|
-
|
|
1627
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Rejecting incoming call acceptance")
|
|
1595
1628
|
pendingCall?.reject("Permissions required for call. Please grant them.")
|
|
1596
1629
|
clearPendingCall()
|
|
1597
1630
|
}
|
|
1598
1631
|
|
|
1599
1632
|
else -> {
|
|
1600
|
-
|
|
1633
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: No pending operations to handle")
|
|
1601
1634
|
clearPendingCall()
|
|
1602
1635
|
}
|
|
1603
1636
|
}
|
|
@@ -1610,10 +1643,10 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1610
1643
|
try {
|
|
1611
1644
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
1612
1645
|
("package:" + activity.packageName).toUri())
|
|
1613
|
-
intent.addCategory(Intent.CATEGORY_DEFAULT)
|
|
1614
|
-
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
1646
|
+
intent.addCategory(Intent.CATEGORY_DEFAULT)
|
|
1647
|
+
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
1615
1648
|
context.startActivity(intent)
|
|
1616
|
-
|
|
1649
|
+
Log.d("StreamCallPlugin", "Opened app details settings (Android 11+)")
|
|
1617
1650
|
|
|
1618
1651
|
// Show toast with specific instructions
|
|
1619
1652
|
runOnMainThread {
|
|
@@ -1625,17 +1658,17 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1625
1658
|
}
|
|
1626
1659
|
return
|
|
1627
1660
|
} catch (e: Exception) {
|
|
1628
|
-
|
|
1661
|
+
Log.w("StreamCallPlugin", "Failed to open app details, falling back", e)
|
|
1629
1662
|
}
|
|
1630
1663
|
}
|
|
1631
1664
|
|
|
1632
1665
|
// Fallback for older Android versions or if the above fails
|
|
1633
|
-
val intent =
|
|
1666
|
+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
1634
1667
|
data = Uri.fromParts("package", context.packageName, null)
|
|
1635
1668
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
1636
1669
|
}
|
|
1637
1670
|
context.startActivity(intent)
|
|
1638
|
-
|
|
1671
|
+
Log.d("StreamCallPlugin", "Opened app settings via fallback")
|
|
1639
1672
|
|
|
1640
1673
|
// Show more specific instructions for older versions
|
|
1641
1674
|
runOnMainThread {
|
|
@@ -1647,15 +1680,15 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1647
1680
|
}
|
|
1648
1681
|
|
|
1649
1682
|
} catch (e: Exception) {
|
|
1650
|
-
|
|
1683
|
+
Log.e("StreamCallPlugin", "Error opening app settings", e)
|
|
1651
1684
|
|
|
1652
1685
|
// Final fallback - open general settings
|
|
1653
1686
|
try {
|
|
1654
|
-
val intent =
|
|
1687
|
+
val intent = Intent(Settings.ACTION_SETTINGS).apply {
|
|
1655
1688
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
1656
1689
|
}
|
|
1657
1690
|
context.startActivity(intent)
|
|
1658
|
-
|
|
1691
|
+
Log.d("StreamCallPlugin", "Opened general settings as final fallback")
|
|
1659
1692
|
|
|
1660
1693
|
runOnMainThread {
|
|
1661
1694
|
android.widget.Toast.makeText(
|
|
@@ -1665,7 +1698,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1665
1698
|
).show()
|
|
1666
1699
|
}
|
|
1667
1700
|
} catch (finalException: Exception) {
|
|
1668
|
-
|
|
1701
|
+
Log.e("StreamCallPlugin", "All settings intents failed", finalException)
|
|
1669
1702
|
runOnMainThread {
|
|
1670
1703
|
android.widget.Toast.makeText(
|
|
1671
1704
|
context,
|
|
@@ -1701,11 +1734,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1701
1734
|
put("success", true)
|
|
1702
1735
|
})
|
|
1703
1736
|
} catch (e: Exception) {
|
|
1704
|
-
|
|
1737
|
+
Log.e("StreamCallPlugin", "Error setting microphone: ${e.message}")
|
|
1705
1738
|
call.reject("Failed to set microphone: ${e.message}")
|
|
1706
1739
|
}
|
|
1707
1740
|
}
|
|
1708
1741
|
} catch (e: Exception) {
|
|
1742
|
+
Log.e("StreamCallPlugin", "Error setting microphone: ${e.message}")
|
|
1709
1743
|
call.reject("StreamVideo not initialized")
|
|
1710
1744
|
}
|
|
1711
1745
|
}
|
|
@@ -1731,11 +1765,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1731
1765
|
put("enabled", enabled)
|
|
1732
1766
|
})
|
|
1733
1767
|
} catch (e: Exception) {
|
|
1734
|
-
|
|
1768
|
+
Log.e("StreamCallPlugin", "Error checking the camera status: ${e.message}")
|
|
1735
1769
|
call.reject("Failed to check if camera is enabled: ${e.message}")
|
|
1736
1770
|
}
|
|
1737
1771
|
}
|
|
1738
1772
|
} catch (e: Exception) {
|
|
1773
|
+
Log.e("StreamCallPlugin", "Error checking camera status: ${e.message}")
|
|
1739
1774
|
call.reject("StreamVideo not initialized")
|
|
1740
1775
|
}
|
|
1741
1776
|
}
|
|
@@ -1748,55 +1783,81 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1748
1783
|
return
|
|
1749
1784
|
}
|
|
1750
1785
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1786
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1787
|
+
if (activeCall == null) {
|
|
1788
|
+
call.reject("No active call")
|
|
1789
|
+
return
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if (!enabled) {
|
|
1793
|
+
// Just disable, no permission needed
|
|
1794
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1795
|
+
try {
|
|
1796
|
+
activeCall.camera.setEnabled(false)
|
|
1797
|
+
call.resolve(JSObject().apply {
|
|
1798
|
+
put("success", true)
|
|
1799
|
+
})
|
|
1800
|
+
} catch (e: Exception) {
|
|
1801
|
+
Log.e("StreamCallPlugin", "Error disabling camera: ${e.message}")
|
|
1802
|
+
call.reject("Failed to disable camera: ${e.message}")
|
|
1803
|
+
}
|
|
1756
1804
|
}
|
|
1805
|
+
return
|
|
1806
|
+
}
|
|
1757
1807
|
|
|
1808
|
+
// From here, enabled is true. We need to check for permission.
|
|
1809
|
+
val cameraPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
|
1810
|
+
if (cameraPermission == PackageManager.PERMISSION_GRANTED) {
|
|
1811
|
+
// Permission is already granted
|
|
1758
1812
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1759
1813
|
try {
|
|
1760
|
-
activeCall.
|
|
1814
|
+
activeCall.camera.setEnabled(true)
|
|
1815
|
+
// When we enable camera, the call is no longer audio-only
|
|
1816
|
+
this@StreamCallPlugin.callIsAudioOnly = false
|
|
1761
1817
|
call.resolve(JSObject().apply {
|
|
1762
1818
|
put("success", true)
|
|
1763
1819
|
})
|
|
1764
1820
|
} catch (e: Exception) {
|
|
1765
|
-
|
|
1766
|
-
call.reject("Failed to
|
|
1821
|
+
Log.e("StreamCallPlugin", "Error enabling camera: ${e.message}")
|
|
1822
|
+
call.reject("Failed to enable camera: ${e.message}")
|
|
1767
1823
|
}
|
|
1768
1824
|
}
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1825
|
+
} else {
|
|
1826
|
+
// Permission is not granted, request it.
|
|
1827
|
+
Log.d("StreamCallPlugin", "Camera permission not granted. Requesting permission.")
|
|
1828
|
+
this.pendingSetCameraCall = call
|
|
1829
|
+
// we are enabling camera, so it's not an audio only call
|
|
1830
|
+
requestPermissions(false)
|
|
1831
|
+
// The call will be resolved/rejected in the permission result handlers
|
|
1771
1832
|
}
|
|
1772
1833
|
}
|
|
1773
1834
|
|
|
1774
1835
|
@OptIn(InternalStreamVideoApi::class)
|
|
1775
1836
|
private suspend fun endCallRaw(call: Call) {
|
|
1776
1837
|
val callId = call.id
|
|
1777
|
-
|
|
1838
|
+
Log.d("StreamCallPlugin", "Attempting to end call $callId")
|
|
1778
1839
|
|
|
1779
1840
|
try {
|
|
1780
1841
|
// Get call information to make the decision
|
|
1781
1842
|
val callInfo = call.get()
|
|
1782
|
-
val callData = callInfo
|
|
1843
|
+
val callData = callInfo.getOrNull()?.call
|
|
1783
1844
|
val currentUserId = streamVideoClient?.userId
|
|
1784
1845
|
val createdBy = callData?.createdBy?.id
|
|
1785
1846
|
val isCreator = createdBy == currentUserId
|
|
1786
1847
|
|
|
1787
1848
|
// Use call.state.totalParticipants to get participant count (as per StreamVideo Android SDK docs)
|
|
1788
|
-
val totalParticipants = call.state.totalParticipants.value
|
|
1789
|
-
val shouldEndCall = isCreator || totalParticipants <=
|
|
1849
|
+
val totalParticipants = call.state.totalParticipants.value
|
|
1850
|
+
val shouldEndCall = isCreator || totalParticipants <= 2
|
|
1790
1851
|
|
|
1791
|
-
|
|
1852
|
+
Log.d("StreamCallPlugin", "Call $callId - Creator: $createdBy, CurrentUser: $currentUserId, IsCreator: $isCreator, TotalParticipants: $totalParticipants, ShouldEnd: $shouldEndCall")
|
|
1792
1853
|
|
|
1793
1854
|
if (shouldEndCall) {
|
|
1794
1855
|
// End the call for everyone if I'm the creator or only 1 person
|
|
1795
|
-
|
|
1856
|
+
Log.d("StreamCallPlugin", "Ending call $callId for all participants (creator: $isCreator, participants: $totalParticipants)")
|
|
1796
1857
|
call.end()
|
|
1797
1858
|
} else {
|
|
1798
1859
|
// Just leave the call if there are more than 1 person and I'm not the creator
|
|
1799
|
-
|
|
1860
|
+
Log.d("StreamCallPlugin", "Leaving call $callId (not creator, >1 participants)")
|
|
1800
1861
|
call.leave()
|
|
1801
1862
|
}
|
|
1802
1863
|
|
|
@@ -1806,7 +1867,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1806
1867
|
}
|
|
1807
1868
|
|
|
1808
1869
|
} catch (e: Exception) {
|
|
1809
|
-
|
|
1870
|
+
Log.e("StreamCallPlugin", "Error getting call info for $callId, defaulting to leave()", e)
|
|
1810
1871
|
// Fallback to leave if we can't determine the call info
|
|
1811
1872
|
call.leave()
|
|
1812
1873
|
}
|
|
@@ -1814,19 +1875,18 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1814
1875
|
// Capture context from the overlayView
|
|
1815
1876
|
val currentContext = overlayView?.context ?: this.savedContext
|
|
1816
1877
|
if (currentContext == null) {
|
|
1817
|
-
|
|
1878
|
+
Log.w("StreamCallPlugin", "Cannot end call $callId because context is null")
|
|
1818
1879
|
return
|
|
1819
1880
|
}
|
|
1820
1881
|
|
|
1821
1882
|
runOnMainThread {
|
|
1822
|
-
|
|
1883
|
+
Log.d("StreamCallPlugin", "Setting overlay invisible after ending call $callId")
|
|
1823
1884
|
|
|
1824
1885
|
|
|
1825
1886
|
currentContext.let { ctx ->
|
|
1826
1887
|
val keyguardManager = ctx.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
1827
1888
|
if (keyguardManager.isKeyguardLocked) {
|
|
1828
1889
|
// we allow kill exclusively here
|
|
1829
|
-
// the idea is that:
|
|
1830
1890
|
// the 'empty' instance of this plugin class gets created in application
|
|
1831
1891
|
// then, it handles a notification and setts the context (this.savedContext)
|
|
1832
1892
|
// if the context is new
|
|
@@ -1838,7 +1898,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1838
1898
|
if (savedCapacitorActivity != null) {
|
|
1839
1899
|
|
|
1840
1900
|
if (savedActivityPaused) {
|
|
1841
|
-
|
|
1901
|
+
Log.d("StreamCallPlugin", "Activity is paused. Adding call ${call.id} to savedCallsToEndOnResume")
|
|
1842
1902
|
savedCallsToEndOnResume.add(call)
|
|
1843
1903
|
} else {
|
|
1844
1904
|
transEndCallRaw(call)
|
|
@@ -1852,7 +1912,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1852
1912
|
bridge?.webView?.setBackgroundColor(Color.WHITE) // Restore webview opacity
|
|
1853
1913
|
|
|
1854
1914
|
// Also hide incoming call view if visible
|
|
1855
|
-
|
|
1915
|
+
Log.d("StreamCallPlugin", "Hiding incoming call view for call $callId")
|
|
1856
1916
|
// No dedicated incoming-call native view anymore; UI handled by web layer
|
|
1857
1917
|
}
|
|
1858
1918
|
|
|
@@ -1863,21 +1923,21 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1863
1923
|
private fun changeActivityAsVisibleOnLockScreen(activity: Activity, visible: Boolean) {
|
|
1864
1924
|
if (visible) {
|
|
1865
1925
|
// Ensure the activity is visible over the lock screen when launched via full-screen intent
|
|
1866
|
-
|
|
1926
|
+
Log.d("StreamCallPlugin", "Mark the mainActivity as visible on the lockscreen")
|
|
1867
1927
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
1868
1928
|
activity.setShowWhenLocked(true)
|
|
1869
1929
|
activity.setTurnScreenOn(true)
|
|
1870
1930
|
} else {
|
|
1871
|
-
activity.
|
|
1931
|
+
activity.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
1872
1932
|
}
|
|
1873
1933
|
} else {
|
|
1874
1934
|
// Ensure the activity is NOT visible over the lock screen when launched via full-screen intent
|
|
1875
|
-
|
|
1935
|
+
Log.d("StreamCallPlugin", "Clear the flag for the mainActivity for visible on the lockscreen")
|
|
1876
1936
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
1877
1937
|
activity.setShowWhenLocked(false)
|
|
1878
1938
|
activity.setTurnScreenOn(false)
|
|
1879
1939
|
} else {
|
|
1880
|
-
activity.
|
|
1940
|
+
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
1881
1941
|
}
|
|
1882
1942
|
}
|
|
1883
1943
|
|
|
@@ -1888,21 +1948,21 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1888
1948
|
val callId = call.id
|
|
1889
1949
|
val savedCapacitorActivity = savedActivity
|
|
1890
1950
|
if (savedCapacitorActivity == null) {
|
|
1891
|
-
|
|
1951
|
+
Log.d("StreamCallPlugin", "Cannot perform transEndCallRaw for call $callId. savedCapacitorActivity is null")
|
|
1892
1952
|
return
|
|
1893
1953
|
}
|
|
1894
|
-
|
|
1954
|
+
Log.d("StreamCallPlugin", "Performing a trans-instance call to end call with id $callId")
|
|
1895
1955
|
if (savedCapacitorActivity !is BridgeActivity) {
|
|
1896
|
-
|
|
1956
|
+
Log.e("StreamCallPlugin", "Saved activity is NOT a Capactor activity. Saved activity class: ${savedCapacitorActivity.javaClass.canonicalName}")
|
|
1897
1957
|
return
|
|
1898
1958
|
}
|
|
1899
1959
|
val plugin = savedCapacitorActivity.bridge.getPlugin("StreamCall")
|
|
1900
1960
|
if (plugin == null) {
|
|
1901
|
-
|
|
1961
|
+
Log.e("StreamCallPlugin", "Plugin with name StreamCall not found?????")
|
|
1902
1962
|
return
|
|
1903
1963
|
}
|
|
1904
1964
|
if (plugin.instance !is StreamCallPlugin) {
|
|
1905
|
-
|
|
1965
|
+
Log.e("StreamCallPlugin", "Plugin found, but invalid instance")
|
|
1906
1966
|
return
|
|
1907
1967
|
}
|
|
1908
1968
|
|
|
@@ -1910,7 +1970,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1910
1970
|
try {
|
|
1911
1971
|
(plugin.instance as StreamCallPlugin).endCallRaw(call)
|
|
1912
1972
|
} catch (e: Exception) {
|
|
1913
|
-
|
|
1973
|
+
Log.e("StreamCallPlugin", "Error ending call on remote instance", e)
|
|
1914
1974
|
}
|
|
1915
1975
|
}
|
|
1916
1976
|
}
|
|
@@ -1925,12 +1985,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1925
1985
|
val callToEnd = activeCall ?: ringingCall
|
|
1926
1986
|
|
|
1927
1987
|
if (callToEnd == null) {
|
|
1928
|
-
|
|
1988
|
+
Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
|
|
1929
1989
|
call.reject("No active call to end")
|
|
1930
1990
|
return
|
|
1931
1991
|
}
|
|
1932
1992
|
|
|
1933
|
-
|
|
1993
|
+
Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
|
|
1934
1994
|
|
|
1935
1995
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1936
1996
|
try {
|
|
@@ -1939,11 +1999,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1939
1999
|
put("success", true)
|
|
1940
2000
|
})
|
|
1941
2001
|
} catch (e: Exception) {
|
|
1942
|
-
|
|
2002
|
+
Log.e("StreamCallPlugin", "Error ending call: ${e.message}")
|
|
1943
2003
|
call.reject("Failed to end call: ${e.message}")
|
|
1944
2004
|
}
|
|
1945
2005
|
}
|
|
1946
2006
|
} catch (e: Exception) {
|
|
2007
|
+
Log.e("StreamCallPlugin", "Error ending call: ${e.message}")
|
|
1947
2008
|
call.reject("StreamVideo not initialized")
|
|
1948
2009
|
}
|
|
1949
2010
|
}
|
|
@@ -1974,20 +2035,23 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1974
2035
|
val callType = call.getString("type") ?: "default"
|
|
1975
2036
|
val shouldRing = call.getBoolean("ring") ?: true
|
|
1976
2037
|
val callId = java.util.UUID.randomUUID().toString()
|
|
1977
|
-
val team = call.getString("team")
|
|
2038
|
+
val team = call.getString("team")
|
|
2039
|
+
val isAudioOnly = custom?.getBoolean("audio_only") ?: false
|
|
2040
|
+
this.callIsAudioOnly = isAudioOnly
|
|
1978
2041
|
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2042
|
+
|
|
2043
|
+
Log.d("StreamCallPlugin", "Creating call:")
|
|
2044
|
+
Log.d("StreamCallPlugin", "- Call ID: $callId")
|
|
2045
|
+
Log.d("StreamCallPlugin", "- Call Type: $callType")
|
|
2046
|
+
Log.d("StreamCallPlugin", "- Users: $userIds")
|
|
2047
|
+
Log.d("StreamCallPlugin", "- Should Ring: $shouldRing")
|
|
1984
2048
|
if (custom != null) {
|
|
1985
|
-
|
|
2049
|
+
Log.d("StreamCallPlugin", "- Custom data: $custom")
|
|
1986
2050
|
}
|
|
1987
2051
|
|
|
1988
2052
|
// Check permissions before creating the call
|
|
1989
|
-
if (!checkPermissions()) {
|
|
1990
|
-
|
|
2053
|
+
if (!checkPermissions(isAudioOnly)) {
|
|
2054
|
+
Log.d("StreamCallPlugin", "Permissions not granted, storing call parameters and requesting permissions")
|
|
1991
2055
|
// Store call parameters for later execution
|
|
1992
2056
|
pendingCall = call
|
|
1993
2057
|
pendingCallUserIds = userIds
|
|
@@ -1999,12 +2063,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1999
2063
|
}
|
|
2000
2064
|
// Reset attempt count for new permission flow
|
|
2001
2065
|
permissionAttemptCount = 0
|
|
2002
|
-
requestPermissions()
|
|
2066
|
+
requestPermissions(isAudioOnly)
|
|
2003
2067
|
return // Don't reject immediately, wait for permission result
|
|
2004
2068
|
}
|
|
2005
2069
|
|
|
2006
2070
|
// Execute call creation immediately if permissions are granted
|
|
2007
|
-
createAndStartCall(call, userIds, callType, shouldRing, team, custom)
|
|
2071
|
+
createAndStartCall(call, userIds, callType, shouldRing, team, custom, isAudioOnly)
|
|
2008
2072
|
} catch (e: Exception) {
|
|
2009
2073
|
call.reject("Failed to make call: ${e.message}")
|
|
2010
2074
|
}
|
|
@@ -2026,7 +2090,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2026
2090
|
|
|
2027
2091
|
callStates[callCid] = callState
|
|
2028
2092
|
|
|
2029
|
-
|
|
2093
|
+
Log.d("StreamCallPlugin", "Started timeout monitor for call $callCid with ${memberIds.size} members")
|
|
2030
2094
|
}
|
|
2031
2095
|
|
|
2032
2096
|
private fun checkCallTimeout(callCid: String) {
|
|
@@ -2036,12 +2100,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2036
2100
|
val elapsedSeconds = (now - callState.createdAt) / 1000
|
|
2037
2101
|
|
|
2038
2102
|
if (elapsedSeconds >= 30) {
|
|
2039
|
-
|
|
2103
|
+
Log.d("StreamCallPlugin", "Call $callCid has timed out after $elapsedSeconds seconds")
|
|
2040
2104
|
|
|
2041
2105
|
val hasAccepted = callState.participantResponses.values.any { it == "accepted" }
|
|
2042
2106
|
|
|
2043
2107
|
if (!hasAccepted) {
|
|
2044
|
-
|
|
2108
|
+
Log.d("StreamCallPlugin", "No one accepted call $callCid, marking all non-responders as missed")
|
|
2045
2109
|
|
|
2046
2110
|
// First, remove the timer to prevent further callbacks
|
|
2047
2111
|
callState.timer?.removeCallbacksAndMessages(null)
|
|
@@ -2072,7 +2136,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2072
2136
|
// Notify that call has ended using helper
|
|
2073
2137
|
updateCallStatusAndNotify(callCid, "ended", null, "timeout")
|
|
2074
2138
|
} catch (e: Exception) {
|
|
2075
|
-
|
|
2139
|
+
Log.e("StreamCallPlugin", "Error ending timed out call", e)
|
|
2076
2140
|
}
|
|
2077
2141
|
}
|
|
2078
2142
|
}
|
|
@@ -2087,7 +2151,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2087
2151
|
|
|
2088
2152
|
if (callState != null) {
|
|
2089
2153
|
// Ensure timer is properly canceled
|
|
2090
|
-
|
|
2154
|
+
Log.d("StreamCallPlugin", "Stopping timer for call: $callCid")
|
|
2091
2155
|
callState.timer?.removeCallbacksAndMessages(null)
|
|
2092
2156
|
callState.timer = null
|
|
2093
2157
|
}
|
|
@@ -2097,13 +2161,13 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2097
2161
|
|
|
2098
2162
|
// Hide UI elements directly without setting content
|
|
2099
2163
|
runOnMainThread {
|
|
2100
|
-
|
|
2164
|
+
Log.d("StreamCallPlugin", "Hiding UI elements for call $callCid (one-time cleanup)")
|
|
2101
2165
|
overlayView?.isVisible = false
|
|
2102
2166
|
// here we will also make sure we don't show on lock screen
|
|
2103
2167
|
changeActivityAsVisibleOnLockScreen(this.activity, false)
|
|
2104
2168
|
}
|
|
2105
2169
|
|
|
2106
|
-
|
|
2170
|
+
Log.d("StreamCallPlugin", "Cleaned up resources for ended call: $callCid")
|
|
2107
2171
|
}
|
|
2108
2172
|
|
|
2109
2173
|
private fun checkAllParticipantsResponded(callCid: String) {
|
|
@@ -2112,14 +2176,14 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2112
2176
|
val totalParticipants = callState.members.size
|
|
2113
2177
|
val responseCount = callState.participantResponses.size
|
|
2114
2178
|
|
|
2115
|
-
|
|
2179
|
+
Log.d("StreamCallPlugin", "Checking responses for call $callCid: $responseCount / $totalParticipants")
|
|
2116
2180
|
|
|
2117
2181
|
val allResponded = responseCount >= totalParticipants
|
|
2118
2182
|
val allRejectedOrMissed = allResponded &&
|
|
2119
2183
|
callState.participantResponses.values.all { it == "rejected" || it == "missed" }
|
|
2120
2184
|
|
|
2121
2185
|
if (allResponded && allRejectedOrMissed) {
|
|
2122
|
-
|
|
2186
|
+
Log.d("StreamCallPlugin", "All participants have rejected or missed the call $callCid")
|
|
2123
2187
|
|
|
2124
2188
|
// Cancel the timer immediately to prevent further callbacks
|
|
2125
2189
|
callState.timer?.removeCallbacksAndMessages(null)
|
|
@@ -2143,7 +2207,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2143
2207
|
// Notify that call has ended using helper
|
|
2144
2208
|
updateCallStatusAndNotify(callCid, "ended", null, "all_rejected_or_missed")
|
|
2145
2209
|
} catch (e: Exception) {
|
|
2146
|
-
|
|
2210
|
+
Log.e("StreamCallPlugin", "Error ending call after all rejected/missed", e)
|
|
2147
2211
|
}
|
|
2148
2212
|
}
|
|
2149
2213
|
}
|
|
@@ -2153,10 +2217,10 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2153
2217
|
|
|
2154
2218
|
private suspend fun magicDeviceDelete(streamVideoClient: StreamVideo) {
|
|
2155
2219
|
try {
|
|
2156
|
-
|
|
2220
|
+
Log.d("StreamCallPlugin", "Starting magicDeviceDelete operation")
|
|
2157
2221
|
|
|
2158
2222
|
FirebaseMessaging.getInstance().token.await()?.let {
|
|
2159
|
-
|
|
2223
|
+
Log.d("StreamCallPlugin", "Found firebase token")
|
|
2160
2224
|
val device = Device(
|
|
2161
2225
|
id = it,
|
|
2162
2226
|
pushProvider = PushProvider.FIREBASE.key,
|
|
@@ -2166,7 +2230,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2166
2230
|
streamVideoClient.deleteDevice(device)
|
|
2167
2231
|
}
|
|
2168
2232
|
} catch (e: Exception) {
|
|
2169
|
-
|
|
2233
|
+
Log.e("StreamCallPlugin", "Error in magicDeviceDelete", e)
|
|
2170
2234
|
}
|
|
2171
2235
|
}
|
|
2172
2236
|
|
|
@@ -2231,12 +2295,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2231
2295
|
|
|
2232
2296
|
try {
|
|
2233
2297
|
saveDynamicApiKey(apiKey)
|
|
2234
|
-
|
|
2298
|
+
Log.d("StreamCallPlugin", "Dynamic API key saved successfully")
|
|
2235
2299
|
call.resolve(JSObject().apply {
|
|
2236
2300
|
put("success", true)
|
|
2237
2301
|
})
|
|
2238
2302
|
} catch (e: Exception) {
|
|
2239
|
-
|
|
2303
|
+
Log.e("StreamCallPlugin", "Error saving dynamic API key", e)
|
|
2240
2304
|
call.reject("Failed to save API key: ${e.message}")
|
|
2241
2305
|
}
|
|
2242
2306
|
}
|
|
@@ -2255,7 +2319,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2255
2319
|
}
|
|
2256
2320
|
})
|
|
2257
2321
|
} catch (e: Exception) {
|
|
2258
|
-
|
|
2322
|
+
Log.e("StreamCallPlugin", "Error getting dynamic API key", e)
|
|
2259
2323
|
call.reject("Failed to get API key: ${e.message}")
|
|
2260
2324
|
}
|
|
2261
2325
|
}
|
|
@@ -2263,17 +2327,9 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2263
2327
|
// Helper functions for managing dynamic API key in SharedPreferences
|
|
2264
2328
|
private fun saveDynamicApiKey(apiKey: String) {
|
|
2265
2329
|
val sharedPrefs = getApiKeyPreferences()
|
|
2266
|
-
sharedPrefs.edit
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
// Helper functions for managing dynamic API key in SharedPreferences
|
|
2272
|
-
private fun clearDynamicApiKey() {
|
|
2273
|
-
val sharedPrefs = getApiKeyPreferences()
|
|
2274
|
-
sharedPrefs.edit()
|
|
2275
|
-
.remove(DYNAMIC_API_KEY_PREF)
|
|
2276
|
-
.apply()
|
|
2330
|
+
sharedPrefs.edit {
|
|
2331
|
+
putString(DYNAMIC_API_KEY_PREF, apiKey)
|
|
2332
|
+
}
|
|
2277
2333
|
}
|
|
2278
2334
|
|
|
2279
2335
|
private fun getDynamicApiKey(): String? {
|
|
@@ -2298,18 +2354,18 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2298
2354
|
// A) Check if the key exists in the custom preference
|
|
2299
2355
|
val dynamicApiKey = getDynamicApiKey(context)
|
|
2300
2356
|
return if (!dynamicApiKey.isNullOrEmpty() && dynamicApiKey.trim().isNotEmpty()) {
|
|
2301
|
-
|
|
2357
|
+
Log.d("StreamCallPlugin", "Using dynamic API key")
|
|
2302
2358
|
dynamicApiKey
|
|
2303
2359
|
} else {
|
|
2304
2360
|
// B) If not, use R.string.CAPACITOR_STREAM_VIDEO_APIKEY
|
|
2305
|
-
|
|
2361
|
+
Log.d("StreamCallPlugin", "Using static API key from resources")
|
|
2306
2362
|
context.getString(R.string.CAPACITOR_STREAM_VIDEO_APIKEY)
|
|
2307
2363
|
}
|
|
2308
2364
|
}
|
|
2309
2365
|
|
|
2310
2366
|
// Helper method to update call status and notify listeners
|
|
2311
2367
|
private fun updateCallStatusAndNotify(callId: String, state: String, userId: String? = null, reason: String? = null, members: List<Map<String, Any>>? = null, caller: Map<String, Any>? = null) {
|
|
2312
|
-
|
|
2368
|
+
Log.d("StreamCallPlugin", "updateCallStatusAndNotify called: callId=$callId, state=$state, userId=$userId, reason=$reason")
|
|
2313
2369
|
// Update stored call info
|
|
2314
2370
|
currentCallId = callId
|
|
2315
2371
|
currentCallState = state
|
|
@@ -2351,6 +2407,13 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2351
2407
|
}
|
|
2352
2408
|
}
|
|
2353
2409
|
|
|
2410
|
+
val eventString = data.toString()
|
|
2411
|
+
if (lastEventSent == eventString) {
|
|
2412
|
+
Log.d("StreamCallPlugin", "Duplicate event detected, not sending: $eventString")
|
|
2413
|
+
return
|
|
2414
|
+
}
|
|
2415
|
+
lastEventSent = eventString
|
|
2416
|
+
|
|
2354
2417
|
// Notify listeners
|
|
2355
2418
|
notifyListeners("callEvent", data)
|
|
2356
2419
|
}
|
|
@@ -2359,13 +2422,18 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2359
2422
|
fun joinCall(call: PluginCall) {
|
|
2360
2423
|
val fragment = callFragment
|
|
2361
2424
|
if (fragment != null && fragment.getCall() != null) {
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
}
|
|
2425
|
+
val activeCall = fragment.getCall()!!
|
|
2426
|
+
// I need to get custom data here, which is async.
|
|
2427
|
+
// The method is not suspend.
|
|
2428
|
+
// Let's launch a coroutine
|
|
2367
2429
|
CoroutineScope(Dispatchers.Main).launch {
|
|
2368
|
-
|
|
2430
|
+
val isAudioOnly = getIsAudioOnly(activeCall)
|
|
2431
|
+
if (!checkPermissions(isAudioOnly)) {
|
|
2432
|
+
requestPermissions(isAudioOnly)
|
|
2433
|
+
call.reject("Permissions required for call. Please grant them.")
|
|
2434
|
+
return@launch
|
|
2435
|
+
}
|
|
2436
|
+
activeCall.join()
|
|
2369
2437
|
call.resolve()
|
|
2370
2438
|
}
|
|
2371
2439
|
} else {
|
|
@@ -2396,19 +2464,21 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2396
2464
|
private val acceptCallReceiver = object : BroadcastReceiver() {
|
|
2397
2465
|
override fun onReceive(context: Context?, intent: Intent?) {
|
|
2398
2466
|
if (intent?.action == "io.getstream.video.android.action.ACCEPT_CALL") {
|
|
2399
|
-
|
|
2467
|
+
Log.d("StreamCallPlugin", "BroadcastReceiver: Received broadcast with action: ${intent.action}")
|
|
2400
2468
|
val cid = intent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
|
|
2401
2469
|
if (cid != null) {
|
|
2402
|
-
|
|
2470
|
+
Log.d("StreamCallPlugin", "BroadcastReceiver: ACCEPT_CALL broadcast received with cid: $cid")
|
|
2403
2471
|
val call = streamVideoClient?.call(id = cid.id, type = cid.type)
|
|
2404
2472
|
if (call != null) {
|
|
2405
|
-
|
|
2473
|
+
Log.d("StreamCallPlugin", "BroadcastReceiver: Accepting call with cid: $cid")
|
|
2406
2474
|
kotlinx.coroutines.GlobalScope.launch {
|
|
2407
|
-
|
|
2475
|
+
val isAudioOnly = getIsAudioOnly(call)
|
|
2476
|
+
this@StreamCallPlugin.callIsAudioOnly = isAudioOnly
|
|
2477
|
+
internalAcceptCall(call, requestPermissionsAfter = !checkPermissions(isAudioOnly))
|
|
2408
2478
|
}
|
|
2409
2479
|
bringAppToForeground()
|
|
2410
2480
|
} else {
|
|
2411
|
-
|
|
2481
|
+
Log.e("StreamCallPlugin", "BroadcastReceiver: Call object is null for cid: $cid")
|
|
2412
2482
|
}
|
|
2413
2483
|
}
|
|
2414
2484
|
}
|
|
@@ -2422,12 +2492,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2422
2492
|
launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
2423
2493
|
if (launchIntent != null) {
|
|
2424
2494
|
ctx.startActivity(launchIntent)
|
|
2425
|
-
|
|
2495
|
+
Log.d("StreamCallPlugin", "bringAppToForeground: Launch intent executed to foreground app")
|
|
2426
2496
|
} else {
|
|
2427
|
-
|
|
2497
|
+
Log.w("StreamCallPlugin", "bringAppToForeground: launchIntent is null")
|
|
2428
2498
|
}
|
|
2429
2499
|
} catch (e: Exception) {
|
|
2430
|
-
|
|
2500
|
+
Log.e("StreamCallPlugin", "bringAppToForeground error", e)
|
|
2431
2501
|
}
|
|
2432
2502
|
}
|
|
2433
2503
|
|
|
@@ -2446,4 +2516,14 @@ public class StreamCallPlugin : Plugin() {
|
|
|
2446
2516
|
private const val API_KEY_PREFS_NAME = "stream_video_api_key_prefs"
|
|
2447
2517
|
private const val DYNAMIC_API_KEY_PREF = "dynamic_api_key"
|
|
2448
2518
|
}
|
|
2519
|
+
|
|
2520
|
+
private suspend fun getIsAudioOnly(call: Call): Boolean {
|
|
2521
|
+
val callInfoResult = call.get()
|
|
2522
|
+
return if (callInfoResult.isSuccess) {
|
|
2523
|
+
val audioOnlyValue = callInfoResult.getOrNull()?.call?.custom?.get("audio_only")
|
|
2524
|
+
audioOnlyValue?.toString() == "true"
|
|
2525
|
+
} else {
|
|
2526
|
+
false
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2449
2529
|
}
|