@qusaieilouti99/call-manager 0.1.56 → 0.1.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallEngine.kt +272 -160
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallForegroundService.kt +13 -2
- package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallManager.kt +58 -33
- package/package.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.margelo.nitro.qusaieilouti99.callmanager
|
|
2
2
|
|
|
3
|
-
import android.app.
|
|
4
|
-
import android.app.Notification
|
|
3
|
+
import android.app.Application
|
|
5
4
|
import android.app.NotificationChannel
|
|
6
5
|
import android.app.NotificationManager
|
|
7
6
|
import android.app.PendingIntent
|
|
@@ -15,7 +14,6 @@ import android.media.AudioDeviceInfo
|
|
|
15
14
|
import android.media.AudioFocusRequest
|
|
16
15
|
import android.media.AudioManager
|
|
17
16
|
import android.media.MediaPlayer
|
|
18
|
-
import android.media.RingtoneManager
|
|
19
17
|
import android.net.Uri
|
|
20
18
|
import android.os.Build
|
|
21
19
|
import android.os.Bundle
|
|
@@ -30,6 +28,9 @@ import android.telecom.PhoneAccountHandle
|
|
|
30
28
|
import android.telecom.TelecomManager
|
|
31
29
|
import android.telecom.VideoProfile
|
|
32
30
|
import android.util.Log
|
|
31
|
+
import androidx.lifecycle.DefaultLifecycleObserver
|
|
32
|
+
import androidx.lifecycle.LifecycleOwner
|
|
33
|
+
import androidx.lifecycle.ProcessLifecycleOwner
|
|
33
34
|
import kotlinx.coroutines.CoroutineScope
|
|
34
35
|
import kotlinx.coroutines.Dispatchers
|
|
35
36
|
import kotlinx.coroutines.launch
|
|
@@ -38,39 +39,40 @@ import org.json.JSONObject
|
|
|
38
39
|
import java.util.concurrent.ConcurrentHashMap
|
|
39
40
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
40
41
|
|
|
41
|
-
object CallEngine {
|
|
42
|
+
object CallEngine : DefaultLifecycleObserver {
|
|
42
43
|
private const val TAG = "CallEngine"
|
|
43
44
|
private const val PHONE_ACCOUNT_ID = "com.qusaieilouti99.callmanager.SELF_MANAGED"
|
|
44
45
|
private const val NOTIF_CHANNEL_ID = "incoming_call_channel"
|
|
45
46
|
private const val NOTIF_ID = 2001
|
|
46
|
-
private const val FOREGROUND_CHANNEL_ID = "call_foreground_channel"
|
|
47
|
-
private const val FOREGROUND_NOTIF_ID = 1001
|
|
48
47
|
|
|
49
|
-
//
|
|
48
|
+
// Core Context Management - Single Source of Truth
|
|
49
|
+
@Volatile
|
|
50
|
+
private var appContext: Context? = null
|
|
51
|
+
private val isInitialized = AtomicBoolean(false)
|
|
52
|
+
|
|
53
|
+
// Audio Management
|
|
54
|
+
private var audioManager: AudioManager? = null
|
|
55
|
+
private var audioFocusRequest: AudioFocusRequest? = null
|
|
56
|
+
private var hasAudioFocus: Boolean = false
|
|
57
|
+
private var isSystemCallActive: Boolean = false
|
|
58
|
+
private var lastAudioRoutesInfo: AudioRoutesInfo? = null
|
|
59
|
+
private var lastMuteState: Boolean = false
|
|
60
|
+
|
|
61
|
+
// Media Management
|
|
50
62
|
private var ringtone: android.media.Ringtone? = null
|
|
51
63
|
private var ringbackPlayer: MediaPlayer? = null
|
|
52
|
-
|
|
64
|
+
|
|
65
|
+
// Power Management
|
|
53
66
|
private var wakeLock: PowerManager.WakeLock? = null
|
|
54
|
-
private var appContext: Context? = null
|
|
55
|
-
private var audioFocusRequest: AudioFocusRequest? = null
|
|
56
67
|
|
|
57
|
-
// Call State Management -
|
|
68
|
+
// Call State Management - Single Source of Truth
|
|
58
69
|
private val activeCalls = ConcurrentHashMap<String, CallInfo>()
|
|
59
70
|
private val telecomConnections = ConcurrentHashMap<String, Connection>()
|
|
60
|
-
|
|
61
|
-
// Separate opaque metadata storage - native doesn't interpret
|
|
62
71
|
private val callMetadata = ConcurrentHashMap<String, String>()
|
|
63
|
-
|
|
64
72
|
private var currentCallId: String? = null
|
|
65
73
|
private var canMakeMultipleCalls: Boolean = false
|
|
66
74
|
|
|
67
|
-
//
|
|
68
|
-
private var lastAudioRoutesInfo: AudioRoutesInfo? = null
|
|
69
|
-
private var lastMuteState: Boolean = false
|
|
70
|
-
private var hasAudioFocus: Boolean = false
|
|
71
|
-
private var isSystemCallActive: Boolean = false
|
|
72
|
-
|
|
73
|
-
// Lock Screen Bypass
|
|
75
|
+
// Lock Screen Management
|
|
74
76
|
private var lockScreenBypassActive = false
|
|
75
77
|
private val lockScreenBypassCallbacks = mutableSetOf<LockScreenBypassCallback>()
|
|
76
78
|
|
|
@@ -78,14 +80,51 @@ object CallEngine {
|
|
|
78
80
|
private var eventHandler: ((CallEventType, String) -> Unit)? = null
|
|
79
81
|
private val cachedEvents = mutableListOf<Pair<CallEventType, String>>()
|
|
80
82
|
|
|
81
|
-
//
|
|
82
|
-
private val
|
|
83
|
+
// Service Management
|
|
84
|
+
private val serviceHandler = Handler(Looper.getMainLooper())
|
|
85
|
+
private var isServiceRunning = false
|
|
83
86
|
|
|
84
87
|
interface LockScreenBypassCallback {
|
|
85
88
|
fun onLockScreenBypassChanged(shouldBypass: Boolean)
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
/**
|
|
92
|
+
* MANDATORY: Initialize CallEngine with Application context
|
|
93
|
+
* This should be called from Application.onCreate() or MainApplication
|
|
94
|
+
*/
|
|
95
|
+
fun initialize(application: Application) {
|
|
96
|
+
synchronized(this) {
|
|
97
|
+
if (isInitialized.get()) {
|
|
98
|
+
Log.d(TAG, "CallEngine already initialized")
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
appContext = application.applicationContext
|
|
103
|
+
audioManager = appContext?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
104
|
+
|
|
105
|
+
// Register lifecycle observer for proper cleanup
|
|
106
|
+
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
|
107
|
+
|
|
108
|
+
// Register audio device callback
|
|
109
|
+
registerAudioDeviceCallback()
|
|
110
|
+
|
|
111
|
+
isInitialized.set(true)
|
|
112
|
+
Log.d(TAG, "CallEngine initialized successfully")
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun onDestroy(owner: LifecycleOwner) {
|
|
117
|
+
Log.d(TAG, "Application lifecycle onDestroy - cleaning up all calls")
|
|
118
|
+
cleanup()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private fun ensureInitialized(): Context {
|
|
122
|
+
return appContext ?: throw IllegalStateException(
|
|
123
|
+
"CallEngine not initialized. Call CallEngine.initialize(application) from your Application.onCreate()"
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Audio Focus Management (Improved) ---
|
|
89
128
|
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
|
|
90
129
|
Log.d(TAG, "Audio focus changed: $focusChange")
|
|
91
130
|
when (focusChange) {
|
|
@@ -118,7 +157,7 @@ object CallEngine {
|
|
|
118
157
|
hasAudioFocus = true
|
|
119
158
|
isSystemCallActive = false
|
|
120
159
|
|
|
121
|
-
|
|
160
|
+
serviceHandler.postDelayed({
|
|
122
161
|
activeCalls.values.filter { it.state == CallState.HELD && it.wasHeldBySystem }.forEach { call ->
|
|
123
162
|
unholdCallInternal(call.callId, resumedBySystem = true)
|
|
124
163
|
}
|
|
@@ -127,7 +166,7 @@ object CallEngine {
|
|
|
127
166
|
}
|
|
128
167
|
|
|
129
168
|
private fun requestAudioFocus(): Boolean {
|
|
130
|
-
val context =
|
|
169
|
+
val context = ensureInitialized()
|
|
131
170
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
132
171
|
|
|
133
172
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
@@ -161,7 +200,7 @@ object CallEngine {
|
|
|
161
200
|
|
|
162
201
|
private fun abandonAudioFocus() {
|
|
163
202
|
val context = appContext ?: return
|
|
164
|
-
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as
|
|
203
|
+
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
165
204
|
|
|
166
205
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
167
206
|
audioFocusRequest?.let { request ->
|
|
@@ -199,66 +238,83 @@ object CallEngine {
|
|
|
199
238
|
}
|
|
200
239
|
}
|
|
201
240
|
|
|
202
|
-
// ---
|
|
203
|
-
fun
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
241
|
+
// --- Service Management (Fixed) ---
|
|
242
|
+
private fun startForegroundService(context: Context) {
|
|
243
|
+
if (isServiceRunning) {
|
|
244
|
+
Log.d(TAG, "Foreground service already running, updating notification")
|
|
245
|
+
updateForegroundNotification()
|
|
246
|
+
return
|
|
247
|
+
}
|
|
210
248
|
|
|
211
|
-
|
|
212
|
-
val
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
Log.d(TAG, "Lock screen bypass state changed: $lockScreenBypassActive")
|
|
216
|
-
lockScreenBypassCallbacks.forEach { callback ->
|
|
217
|
-
try {
|
|
218
|
-
callback.onLockScreenBypassChanged(shouldBypass)
|
|
219
|
-
} catch (e: Exception) {
|
|
220
|
-
Log.w(TAG, "Error notifying lock screen bypass callback", e)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
249
|
+
Log.d(TAG, "Starting CallForegroundService.")
|
|
250
|
+
val currentCall = activeCalls.values.find {
|
|
251
|
+
it.state == CallState.ACTIVE || it.state == CallState.INCOMING ||
|
|
252
|
+
it.state == CallState.DIALING || it.state == CallState.HELD
|
|
223
253
|
}
|
|
224
|
-
}
|
|
225
254
|
|
|
226
|
-
|
|
255
|
+
val intent = Intent(context, CallForegroundService::class.java)
|
|
256
|
+
if (currentCall != null) {
|
|
257
|
+
intent.putExtra("callId", currentCall.callId)
|
|
258
|
+
intent.putExtra("callType", currentCall.callType)
|
|
259
|
+
intent.putExtra("displayName", currentCall.displayName)
|
|
260
|
+
intent.putExtra("state", currentCall.state.name)
|
|
261
|
+
Log.d(TAG, "Starting foreground service with call info: ${currentCall.callId}")
|
|
262
|
+
}
|
|
227
263
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
264
|
+
try {
|
|
265
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
266
|
+
context.startForegroundService(intent)
|
|
267
|
+
} else {
|
|
268
|
+
context.startService(intent)
|
|
269
|
+
}
|
|
270
|
+
isServiceRunning = true
|
|
271
|
+
Log.d(TAG, "Foreground service started successfully")
|
|
272
|
+
} catch (e: Exception) {
|
|
273
|
+
Log.e(TAG, "Failed to start foreground service: ${e.message}", e)
|
|
274
|
+
}
|
|
232
275
|
}
|
|
233
276
|
|
|
234
|
-
fun
|
|
235
|
-
|
|
236
|
-
Log.d(TAG, "
|
|
277
|
+
private fun stopForegroundService(context: Context) {
|
|
278
|
+
if (!isServiceRunning) {
|
|
279
|
+
Log.d(TAG, "Foreground service not running")
|
|
280
|
+
return
|
|
237
281
|
}
|
|
282
|
+
|
|
283
|
+
Log.d(TAG, "Stopping CallForegroundService.")
|
|
284
|
+
val intent = Intent(context, CallForegroundService::class.java)
|
|
285
|
+
context.stopService(intent)
|
|
286
|
+
isServiceRunning = false
|
|
238
287
|
}
|
|
239
288
|
|
|
240
|
-
fun
|
|
289
|
+
private fun updateForegroundNotification() {
|
|
290
|
+
val context = appContext ?: return
|
|
291
|
+
if (!isServiceRunning) return
|
|
241
292
|
|
|
242
|
-
|
|
293
|
+
val activeCall = activeCalls.values.find { it.state == CallState.ACTIVE }
|
|
294
|
+
val heldCall = activeCalls.values.find { it.state == CallState.HELD }
|
|
243
295
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
296
|
+
val callToShow = activeCall ?: heldCall
|
|
297
|
+
callToShow?.let {
|
|
298
|
+
val intent = Intent(context, CallForegroundService::class.java)
|
|
299
|
+
intent.putExtra("UPDATE_NOTIFICATION", true)
|
|
300
|
+
intent.putExtra("callId", it.callId)
|
|
301
|
+
intent.putExtra("callType", it.callType)
|
|
302
|
+
intent.putExtra("displayName", it.displayName)
|
|
303
|
+
intent.putExtra("state", it.state.name)
|
|
249
304
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
305
|
+
try {
|
|
306
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
307
|
+
context.startForegroundService(intent)
|
|
308
|
+
} else {
|
|
309
|
+
context.startService(intent)
|
|
310
|
+
}
|
|
311
|
+
} catch (e: Exception) {
|
|
312
|
+
Log.e(TAG, "Failed to update foreground notification: ${e.message}", e)
|
|
313
|
+
}
|
|
255
314
|
}
|
|
256
|
-
val result = jsonArray.toString()
|
|
257
|
-
Log.d(TAG, "Current call state: $result")
|
|
258
|
-
return result
|
|
259
315
|
}
|
|
260
316
|
|
|
261
|
-
// ---
|
|
317
|
+
// --- Call Management (Fixed Context Issues) ---
|
|
262
318
|
fun reportIncomingCall(
|
|
263
319
|
context: Context,
|
|
264
320
|
callId: String,
|
|
@@ -267,10 +323,9 @@ object CallEngine {
|
|
|
267
323
|
pictureUrl: String? = null,
|
|
268
324
|
metadata: String? = null
|
|
269
325
|
) {
|
|
270
|
-
|
|
326
|
+
ensureInitialized()
|
|
271
327
|
Log.d(TAG, "reportIncomingCall: callId=$callId, type=$callType, name=$displayName")
|
|
272
328
|
|
|
273
|
-
// Store metadata separately if provided
|
|
274
329
|
metadata?.let { callMetadata[callId] = it }
|
|
275
330
|
|
|
276
331
|
// Check for call collision
|
|
@@ -302,6 +357,9 @@ object CallEngine {
|
|
|
302
357
|
currentCallId = callId
|
|
303
358
|
Log.d(TAG, "Call $callId added to activeCalls. State: INCOMING, callType: $callType")
|
|
304
359
|
|
|
360
|
+
// Start foreground service FIRST
|
|
361
|
+
startForegroundService(context)
|
|
362
|
+
|
|
305
363
|
showIncomingCallUI(context, callId, displayName, callType)
|
|
306
364
|
registerPhoneAccount(context)
|
|
307
365
|
|
|
@@ -317,7 +375,6 @@ object CallEngine {
|
|
|
317
375
|
|
|
318
376
|
try {
|
|
319
377
|
telecomManager.addNewIncomingCall(phoneAccountHandle, extras)
|
|
320
|
-
startForegroundService(context)
|
|
321
378
|
Log.d(TAG, "Successfully reported incoming call to TelecomManager for $callId")
|
|
322
379
|
} catch (e: SecurityException) {
|
|
323
380
|
Log.e(TAG, "SecurityException: Failed to report incoming call. Check MANAGE_OWN_CALLS permission: ${e.message}", e)
|
|
@@ -330,7 +387,6 @@ object CallEngine {
|
|
|
330
387
|
updateLockScreenBypass()
|
|
331
388
|
}
|
|
332
389
|
|
|
333
|
-
// --- Outgoing Call Management ---
|
|
334
390
|
fun startOutgoingCall(
|
|
335
391
|
context: Context,
|
|
336
392
|
callId: String,
|
|
@@ -338,7 +394,7 @@ object CallEngine {
|
|
|
338
394
|
targetName: String,
|
|
339
395
|
metadata: String? = null
|
|
340
396
|
) {
|
|
341
|
-
|
|
397
|
+
ensureInitialized()
|
|
342
398
|
Log.d(TAG, "startOutgoingCall: callId=$callId, type=$callType, target=$targetName")
|
|
343
399
|
|
|
344
400
|
metadata?.let { callMetadata[callId] = it }
|
|
@@ -366,6 +422,9 @@ object CallEngine {
|
|
|
366
422
|
currentCallId = callId
|
|
367
423
|
Log.d(TAG, "Call $callId added to activeCalls. State: DIALING, callType: $callType")
|
|
368
424
|
|
|
425
|
+
// Start foreground service FIRST
|
|
426
|
+
startForegroundService(context)
|
|
427
|
+
|
|
369
428
|
registerPhoneAccount(context)
|
|
370
429
|
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
|
371
430
|
val phoneAccountHandle = getPhoneAccountHandle(context)
|
|
@@ -382,7 +441,6 @@ object CallEngine {
|
|
|
382
441
|
|
|
383
442
|
try {
|
|
384
443
|
telecomManager.placeCall(addressUri, extras)
|
|
385
|
-
startForegroundService(context)
|
|
386
444
|
Log.d(TAG, "Successfully reported outgoing call to TelecomManager via placeCall for $callId")
|
|
387
445
|
|
|
388
446
|
requestAudioFocus()
|
|
@@ -401,7 +459,7 @@ object CallEngine {
|
|
|
401
459
|
updateLockScreenBypass()
|
|
402
460
|
}
|
|
403
461
|
|
|
404
|
-
//
|
|
462
|
+
// Fixed: startCall now properly initializes context and starts foreground service
|
|
405
463
|
fun startCall(
|
|
406
464
|
context: Context,
|
|
407
465
|
callId: String,
|
|
@@ -409,7 +467,7 @@ object CallEngine {
|
|
|
409
467
|
targetName: String,
|
|
410
468
|
metadata: String? = null
|
|
411
469
|
) {
|
|
412
|
-
|
|
470
|
+
ensureInitialized()
|
|
413
471
|
Log.d(TAG, "startCall: callId=$callId, type=$callType, target=$targetName")
|
|
414
472
|
|
|
415
473
|
metadata?.let { callMetadata[callId] = it }
|
|
@@ -432,10 +490,12 @@ object CallEngine {
|
|
|
432
490
|
currentCallId = callId
|
|
433
491
|
Log.d(TAG, "Call $callId started as ACTIVE, callType: $callType")
|
|
434
492
|
|
|
493
|
+
// Start foreground service FIRST
|
|
494
|
+
startForegroundService(context)
|
|
495
|
+
|
|
435
496
|
registerPhoneAccount(context)
|
|
436
497
|
requestAudioFocus()
|
|
437
498
|
bringAppToForeground(context)
|
|
438
|
-
startForegroundService(context)
|
|
439
499
|
keepScreenAwake(context, true)
|
|
440
500
|
setInitialAudioRoute(context, callType)
|
|
441
501
|
updateLockScreenBypass()
|
|
@@ -444,13 +504,15 @@ object CallEngine {
|
|
|
444
504
|
emitCallAnsweredWithMetadata(callId)
|
|
445
505
|
}
|
|
446
506
|
|
|
447
|
-
//
|
|
507
|
+
// Fixed: Context management for all operations
|
|
448
508
|
fun callAnsweredFromJS(context: Context, callId: String) {
|
|
509
|
+
ensureInitialized()
|
|
449
510
|
Log.d(TAG, "callAnsweredFromJS: $callId - remote party answered")
|
|
450
511
|
coreCallAnswered(context, callId, isLocalAnswer = false)
|
|
451
512
|
}
|
|
452
513
|
|
|
453
514
|
fun answerCall(context: Context, callId: String) {
|
|
515
|
+
ensureInitialized()
|
|
454
516
|
Log.d(TAG, "answerCall: $callId - local party answering")
|
|
455
517
|
coreCallAnswered(context, callId, isLocalAnswer = true)
|
|
456
518
|
}
|
|
@@ -513,13 +575,13 @@ object CallEngine {
|
|
|
513
575
|
})
|
|
514
576
|
}
|
|
515
577
|
|
|
516
|
-
// --- Call Control Methods ---
|
|
517
578
|
fun holdCall(context: Context, callId: String) {
|
|
579
|
+
ensureInitialized()
|
|
518
580
|
holdCallInternal(callId, heldBySystem = false)
|
|
519
581
|
}
|
|
520
582
|
|
|
521
|
-
// NEW: Set hold state from JS
|
|
522
583
|
fun setOnHold(context: Context, callId: String, onHold: Boolean) {
|
|
584
|
+
ensureInitialized()
|
|
523
585
|
Log.d(TAG, "setOnHold: $callId, onHold: $onHold")
|
|
524
586
|
|
|
525
587
|
val callInfo = activeCalls[callId]
|
|
@@ -561,6 +623,7 @@ object CallEngine {
|
|
|
561
623
|
}
|
|
562
624
|
|
|
563
625
|
fun unholdCall(context: Context, callId: String) {
|
|
626
|
+
ensureInitialized()
|
|
564
627
|
unholdCallInternal(callId, resumedBySystem = false)
|
|
565
628
|
}
|
|
566
629
|
|
|
@@ -572,10 +635,12 @@ object CallEngine {
|
|
|
572
635
|
return
|
|
573
636
|
}
|
|
574
637
|
|
|
638
|
+
// Fixed: Don't emit UNHOLD_FAILED for system-held calls or when we have focus
|
|
575
639
|
if (!hasAudioFocus && !resumedBySystem) {
|
|
576
640
|
Log.d(TAG, "Attempting to request audio focus for unhold")
|
|
577
641
|
if (!requestAudioFocus()) {
|
|
578
642
|
Log.w(TAG, "Failed to get audio focus for unhold")
|
|
643
|
+
// Only emit UNHOLD_FAILED for user-initiated unhold attempts
|
|
579
644
|
emitEvent(CallEventType.CALL_UNHOLD_FAILED, JSONObject().apply {
|
|
580
645
|
put("callId", callId)
|
|
581
646
|
put("reason", "Could not obtain audio focus")
|
|
@@ -600,15 +665,17 @@ object CallEngine {
|
|
|
600
665
|
}
|
|
601
666
|
|
|
602
667
|
fun muteCall(context: Context, callId: String) {
|
|
668
|
+
ensureInitialized()
|
|
603
669
|
setMutedInternal(context, callId, true)
|
|
604
670
|
}
|
|
605
671
|
|
|
606
672
|
fun unmuteCall(context: Context, callId: String) {
|
|
673
|
+
ensureInitialized()
|
|
607
674
|
setMutedInternal(context, callId, false)
|
|
608
675
|
}
|
|
609
676
|
|
|
610
|
-
// NEW: Set mute state from JS
|
|
611
677
|
fun setMuted(context: Context, callId: String, muted: Boolean) {
|
|
678
|
+
ensureInitialized()
|
|
612
679
|
Log.d(TAG, "setMuted: $callId, muted: $muted")
|
|
613
680
|
setMutedInternal(context, callId, muted)
|
|
614
681
|
}
|
|
@@ -633,15 +700,14 @@ object CallEngine {
|
|
|
633
700
|
}
|
|
634
701
|
}
|
|
635
702
|
|
|
636
|
-
// --- Call End Management ---
|
|
637
703
|
fun endCall(context: Context, callId: String) {
|
|
638
|
-
|
|
704
|
+
ensureInitialized()
|
|
639
705
|
Log.d(TAG, "endCall: $callId")
|
|
640
706
|
endCallInternal(callId)
|
|
641
707
|
}
|
|
642
708
|
|
|
643
|
-
// NEW: End all calls
|
|
644
709
|
fun endAllCalls(context: Context) {
|
|
710
|
+
ensureInitialized()
|
|
645
711
|
Log.d(TAG, "endAllCalls: Ending all active calls.")
|
|
646
712
|
if (activeCalls.isEmpty()) {
|
|
647
713
|
Log.d(TAG, "No active calls, nothing to do.")
|
|
@@ -715,13 +781,17 @@ object CallEngine {
|
|
|
715
781
|
})
|
|
716
782
|
}
|
|
717
783
|
|
|
784
|
+
private fun finalCleanup(context: Context) {
|
|
785
|
+
Log.d(TAG, "Performing final cleanup - no active calls remaining")
|
|
786
|
+
stopForegroundService(context)
|
|
787
|
+
keepScreenAwake(context, false)
|
|
788
|
+
resetAudioMode(context)
|
|
789
|
+
isSystemCallActive = false
|
|
790
|
+
}
|
|
791
|
+
|
|
718
792
|
// --- Audio Management ---
|
|
719
793
|
fun getAudioDevices(): AudioRoutesInfo {
|
|
720
|
-
val context =
|
|
721
|
-
Log.e(TAG, "getAudioDevices: appContext is not set. Returning default.")
|
|
722
|
-
return AudioRoutesInfo(emptyArray(), "Unknown")
|
|
723
|
-
}
|
|
724
|
-
|
|
794
|
+
val context = ensureInitialized()
|
|
725
795
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: run {
|
|
726
796
|
Log.e(TAG, "getAudioDevices: AudioManager is null. Returning default.")
|
|
727
797
|
return AudioRoutesInfo(emptyArray(), "Unknown")
|
|
@@ -765,6 +835,7 @@ object CallEngine {
|
|
|
765
835
|
}
|
|
766
836
|
|
|
767
837
|
fun setAudioRoute(context: Context, route: String) {
|
|
838
|
+
ensureInitialized()
|
|
768
839
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
769
840
|
Log.d(TAG, "Attempting to set audio route to: $route. Current mode: ${audioManager?.mode}")
|
|
770
841
|
|
|
@@ -856,14 +927,15 @@ object CallEngine {
|
|
|
856
927
|
}
|
|
857
928
|
}
|
|
858
929
|
|
|
859
|
-
fun registerAudioDeviceCallback(
|
|
860
|
-
|
|
930
|
+
private fun registerAudioDeviceCallback() {
|
|
931
|
+
val context = appContext ?: return
|
|
861
932
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
862
933
|
audioManager?.registerAudioDeviceCallback(audioDeviceCallback, null)
|
|
863
934
|
Log.d(TAG, "Audio device callback registered.")
|
|
864
935
|
}
|
|
865
936
|
|
|
866
|
-
fun unregisterAudioDeviceCallback(
|
|
937
|
+
private fun unregisterAudioDeviceCallback() {
|
|
938
|
+
val context = appContext ?: return
|
|
867
939
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
868
940
|
audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
|
|
869
941
|
Log.d(TAG, "Audio device callback unregistered.")
|
|
@@ -942,6 +1014,63 @@ object CallEngine {
|
|
|
942
1014
|
})
|
|
943
1015
|
}
|
|
944
1016
|
|
|
1017
|
+
// --- Lock Screen Bypass Management ---
|
|
1018
|
+
fun registerLockScreenBypassCallback(callback: LockScreenBypassCallback) {
|
|
1019
|
+
lockScreenBypassCallbacks.add(callback)
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
fun unregisterLockScreenBypassCallback(callback: LockScreenBypassCallback) {
|
|
1023
|
+
lockScreenBypassCallbacks.remove(callback)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
private fun updateLockScreenBypass() {
|
|
1027
|
+
val shouldBypass = isCallActive()
|
|
1028
|
+
if (lockScreenBypassActive != shouldBypass) {
|
|
1029
|
+
lockScreenBypassActive = shouldBypass
|
|
1030
|
+
Log.d(TAG, "Lock screen bypass state changed: $lockScreenBypassActive")
|
|
1031
|
+
lockScreenBypassCallbacks.forEach { callback ->
|
|
1032
|
+
try {
|
|
1033
|
+
callback.onLockScreenBypassChanged(shouldBypass)
|
|
1034
|
+
} catch (e: Exception) {
|
|
1035
|
+
Log.w(TAG, "Error notifying lock screen bypass callback", e)
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
fun isLockScreenBypassActive(): Boolean = lockScreenBypassActive
|
|
1042
|
+
|
|
1043
|
+
// --- Telecom Connection Management ---
|
|
1044
|
+
fun addTelecomConnection(callId: String, connection: Connection) {
|
|
1045
|
+
telecomConnections[callId] = connection
|
|
1046
|
+
Log.d(TAG, "Added Telecom Connection for callId: $callId. Total: ${telecomConnections.size}")
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
fun removeTelecomConnection(callId: String) {
|
|
1050
|
+
telecomConnections.remove(callId)?.let {
|
|
1051
|
+
Log.d(TAG, "Removed Telecom Connection for callId: $callId. Total: ${telecomConnections.size}")
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
fun getTelecomConnection(callId: String): Connection? = telecomConnections[callId]
|
|
1056
|
+
|
|
1057
|
+
// --- Public API ---
|
|
1058
|
+
fun setCanMakeMultipleCalls(allow: Boolean) {
|
|
1059
|
+
canMakeMultipleCalls = allow
|
|
1060
|
+
Log.d(TAG, "canMakeMultipleCalls set to: $allow")
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
fun getCurrentCallState(): String {
|
|
1064
|
+
val calls = getActiveCalls()
|
|
1065
|
+
val jsonArray = JSONArray()
|
|
1066
|
+
calls.forEach {
|
|
1067
|
+
jsonArray.put(it.toJsonObject())
|
|
1068
|
+
}
|
|
1069
|
+
val result = jsonArray.toString()
|
|
1070
|
+
Log.d(TAG, "Current call state: $result")
|
|
1071
|
+
return result
|
|
1072
|
+
}
|
|
1073
|
+
|
|
945
1074
|
// --- Notification Management ---
|
|
946
1075
|
private fun createNotificationChannel(context: Context) {
|
|
947
1076
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
@@ -957,7 +1086,7 @@ object CallEngine {
|
|
|
957
1086
|
|
|
958
1087
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
959
1088
|
channel.setSound(
|
|
960
|
-
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
|
|
1089
|
+
android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_RINGTONE),
|
|
961
1090
|
AudioAttributes.Builder()
|
|
962
1091
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
|
963
1092
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
@@ -1010,20 +1139,20 @@ object CallEngine {
|
|
|
1010
1139
|
|
|
1011
1140
|
val notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
1012
1141
|
val person = android.app.Person.Builder().setName(callerName).setImportant(true).build()
|
|
1013
|
-
Notification.Builder(context, NOTIF_CHANNEL_ID)
|
|
1142
|
+
android.app.Notification.Builder(context, NOTIF_CHANNEL_ID)
|
|
1014
1143
|
.setSmallIcon(android.R.drawable.sym_call_incoming)
|
|
1015
|
-
.setStyle(Notification.CallStyle.forIncomingCall(person, declinePendingIntent, answerPendingIntent))
|
|
1144
|
+
.setStyle(android.app.Notification.CallStyle.forIncomingCall(person, declinePendingIntent, answerPendingIntent))
|
|
1016
1145
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
1017
1146
|
.setOngoing(true)
|
|
1018
1147
|
.setAutoCancel(false)
|
|
1019
1148
|
.build()
|
|
1020
1149
|
} else {
|
|
1021
|
-
Notification.Builder(context, NOTIF_CHANNEL_ID)
|
|
1150
|
+
android.app.Notification.Builder(context, NOTIF_CHANNEL_ID)
|
|
1022
1151
|
.setSmallIcon(android.R.drawable.sym_call_incoming)
|
|
1023
1152
|
.setContentTitle("Incoming Call")
|
|
1024
1153
|
.setContentText(callerName)
|
|
1025
|
-
.setPriority(Notification.PRIORITY_HIGH)
|
|
1026
|
-
.setCategory(Notification.CATEGORY_CALL)
|
|
1154
|
+
.setPriority(android.app.Notification.PRIORITY_HIGH)
|
|
1155
|
+
.setCategory(android.app.Notification.CATEGORY_CALL)
|
|
1027
1156
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
1028
1157
|
.addAction(android.R.drawable.sym_action_call, "Answer", answerPendingIntent)
|
|
1029
1158
|
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Decline", declinePendingIntent)
|
|
@@ -1048,38 +1177,6 @@ object CallEngine {
|
|
|
1048
1177
|
stopRingtone()
|
|
1049
1178
|
}
|
|
1050
1179
|
|
|
1051
|
-
// --- Service Management ---
|
|
1052
|
-
fun startForegroundService(context: Context) {
|
|
1053
|
-
Log.d(TAG, "Starting CallForegroundService.")
|
|
1054
|
-
|
|
1055
|
-
val currentCall = activeCalls.values.find {
|
|
1056
|
-
it.state == CallState.ACTIVE || it.state == CallState.INCOMING ||
|
|
1057
|
-
it.state == CallState.DIALING || it.state == CallState.HELD
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
val intent = Intent(context, CallForegroundService::class.java)
|
|
1061
|
-
|
|
1062
|
-
if (currentCall != null) {
|
|
1063
|
-
intent.putExtra("callId", currentCall.callId)
|
|
1064
|
-
intent.putExtra("callType", currentCall.callType)
|
|
1065
|
-
intent.putExtra("displayName", currentCall.displayName)
|
|
1066
|
-
intent.putExtra("state", currentCall.state.name)
|
|
1067
|
-
Log.d(TAG, "Starting foreground service with call info: ${currentCall.callId}")
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1071
|
-
context.startForegroundService(intent)
|
|
1072
|
-
} else {
|
|
1073
|
-
context.startService(intent)
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
fun stopForegroundService(context: Context) {
|
|
1078
|
-
Log.d(TAG, "Stopping CallForegroundService.")
|
|
1079
|
-
val intent = Intent(context, CallForegroundService::class.java)
|
|
1080
|
-
context.stopService(intent)
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
1180
|
fun bringAppToForeground(context: Context) {
|
|
1084
1181
|
val packageName = context.packageName
|
|
1085
1182
|
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
|
|
@@ -1096,7 +1193,7 @@ object CallEngine {
|
|
|
1096
1193
|
|
|
1097
1194
|
try {
|
|
1098
1195
|
context.startActivity(launchIntent)
|
|
1099
|
-
|
|
1196
|
+
serviceHandler.postDelayed({
|
|
1100
1197
|
updateLockScreenBypass()
|
|
1101
1198
|
}, 100)
|
|
1102
1199
|
} catch (e: Exception) {
|
|
@@ -1143,8 +1240,8 @@ object CallEngine {
|
|
|
1143
1240
|
|
|
1144
1241
|
try {
|
|
1145
1242
|
Log.d(TAG, "Playing ringtone (for Android < S).")
|
|
1146
|
-
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
1147
|
-
ringtone = RingtoneManager.getRingtone(context, uri)
|
|
1243
|
+
val uri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_RINGTONE)
|
|
1244
|
+
ringtone = android.media.RingtoneManager.getRingtone(context, uri)
|
|
1148
1245
|
ringtone?.audioAttributes = AudioAttributes.Builder()
|
|
1149
1246
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
|
1150
1247
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
@@ -1211,33 +1308,48 @@ object CallEngine {
|
|
|
1211
1308
|
}
|
|
1212
1309
|
}
|
|
1213
1310
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
val heldCall = activeCalls.values.find { it.state == CallState.HELD }
|
|
1311
|
+
// --- Cleanup ---
|
|
1312
|
+
private fun cleanup() {
|
|
1313
|
+
Log.d(TAG, "Cleaning up CallEngine")
|
|
1218
1314
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
intent.putExtra("callId", it.callId)
|
|
1224
|
-
intent.putExtra("callType", it.callType)
|
|
1225
|
-
intent.putExtra("displayName", it.displayName)
|
|
1226
|
-
intent.putExtra("state", it.state.name)
|
|
1315
|
+
// End all calls
|
|
1316
|
+
activeCalls.keys.toList().forEach { callId ->
|
|
1317
|
+
endCallInternal(callId)
|
|
1318
|
+
}
|
|
1227
1319
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1320
|
+
// Clear all collections
|
|
1321
|
+
activeCalls.clear()
|
|
1322
|
+
telecomConnections.clear()
|
|
1323
|
+
callMetadata.clear()
|
|
1324
|
+
lockScreenBypassCallbacks.clear()
|
|
1325
|
+
cachedEvents.clear()
|
|
1326
|
+
|
|
1327
|
+
// Stop media
|
|
1328
|
+
stopRingtone()
|
|
1329
|
+
stopRingback()
|
|
1330
|
+
|
|
1331
|
+
// Release resources
|
|
1332
|
+
wakeLock?.let {
|
|
1333
|
+
if (it.isHeld) it.release()
|
|
1233
1334
|
}
|
|
1234
|
-
|
|
1335
|
+
wakeLock = null
|
|
1235
1336
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1337
|
+
// Audio cleanup
|
|
1338
|
+
abandonAudioFocus()
|
|
1339
|
+
appContext?.let {
|
|
1340
|
+
resetAudioMode(it)
|
|
1341
|
+
unregisterAudioDeviceCallback()
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Service cleanup
|
|
1345
|
+
appContext?.let { stopForegroundService(it) }
|
|
1346
|
+
|
|
1347
|
+
currentCallId = null
|
|
1348
|
+
lockScreenBypassActive = false
|
|
1349
|
+
hasAudioFocus = false
|
|
1241
1350
|
isSystemCallActive = false
|
|
1351
|
+
isServiceRunning = false
|
|
1352
|
+
|
|
1353
|
+
Log.d(TAG, "CallEngine cleanup completed")
|
|
1242
1354
|
}
|
|
1243
1355
|
}
|
package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallForegroundService.kt
CHANGED
|
@@ -29,6 +29,7 @@ class CallForegroundService : Service() {
|
|
|
29
29
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
30
30
|
Log.d(TAG, "Service onStartCommand")
|
|
31
31
|
|
|
32
|
+
val isUpdate = intent?.getBooleanExtra("UPDATE_NOTIFICATION", false) ?: false
|
|
32
33
|
val callId = intent?.getStringExtra("callId")
|
|
33
34
|
val callType = intent?.getStringExtra("callType")
|
|
34
35
|
val displayName = intent?.getStringExtra("displayName")
|
|
@@ -42,7 +43,13 @@ class CallForegroundService : Service() {
|
|
|
42
43
|
buildBasicNotification()
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
if (!isUpdate) {
|
|
47
|
+
startForeground(NOTIFICATION_ID, notification)
|
|
48
|
+
} else {
|
|
49
|
+
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
50
|
+
notificationManager.notify(NOTIFICATION_ID, notification)
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
return START_STICKY
|
|
47
54
|
}
|
|
48
55
|
|
|
@@ -172,7 +179,11 @@ class CallForegroundService : Service() {
|
|
|
172
179
|
|
|
173
180
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
174
181
|
Log.d(TAG, "onTaskRemoved: User swiped app from recents. Ending all calls.")
|
|
175
|
-
|
|
182
|
+
try {
|
|
183
|
+
CallEngine.endAllCalls(this)
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
Log.e(TAG, "Error ending calls on task removed: ${e.message}", e)
|
|
186
|
+
}
|
|
176
187
|
super.onTaskRemoved(rootIntent)
|
|
177
188
|
stopSelf()
|
|
178
189
|
}
|
|
@@ -10,47 +10,57 @@ class CallManager : HybridCallManagerSpec() {
|
|
|
10
10
|
|
|
11
11
|
override fun endCall(callId: String) {
|
|
12
12
|
Log.d(TAG, "endCall requested for callId: $callId")
|
|
13
|
-
|
|
14
|
-
CallEngine.
|
|
15
|
-
|
|
13
|
+
try {
|
|
14
|
+
val context = CallEngine.ensureInitialized()
|
|
15
|
+
CallEngine.endCall(context, callId)
|
|
16
|
+
} catch (e: IllegalStateException) {
|
|
17
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
18
|
+
}
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
override fun endAllCalls() {
|
|
19
22
|
Log.d(TAG, "endAllCalls requested")
|
|
20
|
-
|
|
21
|
-
CallEngine.
|
|
22
|
-
|
|
23
|
+
try {
|
|
24
|
+
val context = CallEngine.ensureInitialized()
|
|
25
|
+
CallEngine.endAllCalls(context)
|
|
26
|
+
} catch (e: IllegalStateException) {
|
|
27
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
28
|
+
}
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
override fun silenceRingtone() {
|
|
26
32
|
Log.d(TAG, "silenceRingtone requested.")
|
|
27
|
-
CallEngine.
|
|
28
|
-
CallEngine.stopRingtone()
|
|
29
|
-
} ?: Log.e(TAG, "App context not set for silenceRingtone.")
|
|
33
|
+
CallEngine.stopRingtone()
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
override fun getAudioDevices(): AudioRoutesInfo {
|
|
33
37
|
Log.d(TAG, "getAudioDevices requested.")
|
|
34
|
-
return
|
|
38
|
+
return try {
|
|
35
39
|
CallEngine.getAudioDevices()
|
|
36
|
-
}
|
|
37
|
-
Log.e(TAG, "
|
|
40
|
+
} catch (e: IllegalStateException) {
|
|
41
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
38
42
|
AudioRoutesInfo(emptyArray(), "Unknown")
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
override fun setAudioRoute(route: String) {
|
|
43
47
|
Log.d(TAG, "setAudioRoute requested for route: $route")
|
|
44
|
-
|
|
45
|
-
CallEngine.
|
|
46
|
-
|
|
48
|
+
try {
|
|
49
|
+
val context = CallEngine.ensureInitialized()
|
|
50
|
+
CallEngine.setAudioRoute(context, route)
|
|
51
|
+
} catch (e: IllegalStateException) {
|
|
52
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
override fun keepScreenAwake(keepAwake: Boolean) {
|
|
50
57
|
Log.d(TAG, "keepScreenAwake requested: $keepAwake")
|
|
51
|
-
|
|
52
|
-
CallEngine.
|
|
53
|
-
|
|
58
|
+
try {
|
|
59
|
+
val context = CallEngine.ensureInitialized()
|
|
60
|
+
CallEngine.keepScreenAwake(context, keepAwake)
|
|
61
|
+
} catch (e: IllegalStateException) {
|
|
62
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
override fun addListener(
|
|
@@ -66,36 +76,51 @@ class CallManager : HybridCallManagerSpec() {
|
|
|
66
76
|
|
|
67
77
|
override fun startOutgoingCall(callId: String, callType: String, targetName: String, metadata: String?) {
|
|
68
78
|
Log.d(TAG, "startOutgoingCall requested: callId=$callId, callType=$callType, targetName=$targetName")
|
|
69
|
-
|
|
70
|
-
CallEngine.
|
|
71
|
-
|
|
79
|
+
try {
|
|
80
|
+
val context = CallEngine.ensureInitialized()
|
|
81
|
+
CallEngine.startOutgoingCall(context, callId, callType, targetName, metadata)
|
|
82
|
+
} catch (e: IllegalStateException) {
|
|
83
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
84
|
+
}
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
override fun startCall(callId: String, callType: String, targetName: String, metadata: String?) {
|
|
75
88
|
Log.d(TAG, "startCall requested: callId=$callId, callType=$callType, targetName=$targetName")
|
|
76
|
-
|
|
77
|
-
CallEngine.
|
|
78
|
-
|
|
89
|
+
try {
|
|
90
|
+
val context = CallEngine.ensureInitialized()
|
|
91
|
+
CallEngine.startCall(context, callId, callType, targetName, metadata)
|
|
92
|
+
} catch (e: IllegalStateException) {
|
|
93
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
94
|
+
}
|
|
79
95
|
}
|
|
80
96
|
|
|
81
97
|
override fun callAnswered(callId: String) {
|
|
82
98
|
Log.d(TAG, "callAnswered (from JS) requested for callId: $callId")
|
|
83
|
-
|
|
84
|
-
CallEngine.
|
|
85
|
-
|
|
99
|
+
try {
|
|
100
|
+
val context = CallEngine.ensureInitialized()
|
|
101
|
+
CallEngine.callAnsweredFromJS(context, callId)
|
|
102
|
+
} catch (e: IllegalStateException) {
|
|
103
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
104
|
+
}
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
override fun setOnHold(callId: String, onHold: Boolean) {
|
|
89
108
|
Log.d(TAG, "setOnHold requested for callId: $callId, onHold: $onHold")
|
|
90
|
-
|
|
91
|
-
CallEngine.
|
|
92
|
-
|
|
109
|
+
try {
|
|
110
|
+
val context = CallEngine.ensureInitialized()
|
|
111
|
+
CallEngine.setOnHold(context, callId, onHold)
|
|
112
|
+
} catch (e: IllegalStateException) {
|
|
113
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
114
|
+
}
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
override fun setMuted(callId: String, muted: Boolean) {
|
|
96
118
|
Log.d(TAG, "setMuted requested for callId: $callId, muted: $muted")
|
|
97
|
-
|
|
98
|
-
CallEngine.
|
|
99
|
-
|
|
119
|
+
try {
|
|
120
|
+
val context = CallEngine.ensureInitialized()
|
|
121
|
+
CallEngine.setMuted(context, callId, muted)
|
|
122
|
+
} catch (e: IllegalStateException) {
|
|
123
|
+
Log.e(TAG, "CallEngine not initialized: ${e.message}")
|
|
124
|
+
}
|
|
100
125
|
}
|
|
101
126
|
}
|