@qusaieilouti99/call-manager 0.1.67 → 0.1.68
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.
|
@@ -13,7 +13,6 @@ import android.graphics.Color
|
|
|
13
13
|
import android.media.AudioAttributes
|
|
14
14
|
import android.media.AudioDeviceCallback
|
|
15
15
|
import android.media.AudioDeviceInfo
|
|
16
|
-
import android.media.AudioFocusRequest
|
|
17
16
|
import android.media.AudioManager
|
|
18
17
|
import android.media.MediaPlayer
|
|
19
18
|
import android.media.RingtoneManager
|
|
@@ -31,7 +30,6 @@ import android.telecom.PhoneAccountHandle
|
|
|
31
30
|
import android.telecom.TelecomManager
|
|
32
31
|
import android.telecom.VideoProfile
|
|
33
32
|
import android.util.Log
|
|
34
|
-
import androidx.annotation.RequiresApi
|
|
35
33
|
import kotlinx.coroutines.CoroutineScope
|
|
36
34
|
import kotlinx.coroutines.Dispatchers
|
|
37
35
|
import kotlinx.coroutines.launch
|
|
@@ -52,16 +50,11 @@ object CallEngine {
|
|
|
52
50
|
private val isInitialized = AtomicBoolean(false)
|
|
53
51
|
private val initializationLock = Any()
|
|
54
52
|
|
|
55
|
-
//
|
|
53
|
+
// Simplified Audio & Media Management (NO MANUAL AUDIO FOCUS)
|
|
56
54
|
private var ringtone: android.media.Ringtone? = null
|
|
57
55
|
private var ringbackPlayer: MediaPlayer? = null
|
|
58
56
|
private var audioManager: AudioManager? = null
|
|
59
57
|
private var wakeLock: PowerManager.WakeLock? = null
|
|
60
|
-
private var audioFocusRequest: AudioFocusRequest? = null
|
|
61
|
-
private var hasAudioFocus: Boolean = false
|
|
62
|
-
private val audioFocusRetryHandler = Handler(Looper.getMainLooper())
|
|
63
|
-
private var audioFocusRetryCount = 0
|
|
64
|
-
private val MAX_AUDIO_FOCUS_RETRIES = 3
|
|
65
58
|
|
|
66
59
|
// Call State Management
|
|
67
60
|
private val activeCalls = ConcurrentHashMap<String, CallInfo>()
|
|
@@ -86,38 +79,6 @@ object CallEngine {
|
|
|
86
79
|
fun onLockScreenBypassChanged(shouldBypass: Boolean)
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
// Enhanced Audio Focus Change Listener for Self-Managed Calls
|
|
90
|
-
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
|
|
91
|
-
Log.d(TAG, "Audio focus changed: $focusChange")
|
|
92
|
-
when (focusChange) {
|
|
93
|
-
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
94
|
-
Log.d(TAG, "Audio focus gained")
|
|
95
|
-
hasAudioFocus = true
|
|
96
|
-
audioFocusRetryCount = 0
|
|
97
|
-
|
|
98
|
-
// Resume any system-held calls after a short delay
|
|
99
|
-
Handler(Looper.getMainLooper()).postDelayed({
|
|
100
|
-
resumeSystemHeldCalls()
|
|
101
|
-
}, 500)
|
|
102
|
-
}
|
|
103
|
-
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
104
|
-
Log.d(TAG, "Permanent audio focus loss - holding active calls")
|
|
105
|
-
hasAudioFocus = false
|
|
106
|
-
holdAllActiveCalls(heldBySystem = true)
|
|
107
|
-
}
|
|
108
|
-
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
109
|
-
Log.d(TAG, "Transient audio focus loss - holding calls temporarily")
|
|
110
|
-
hasAudioFocus = false
|
|
111
|
-
holdAllActiveCalls(heldBySystem = true)
|
|
112
|
-
}
|
|
113
|
-
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
114
|
-
Log.d(TAG, "Transient audio focus loss (can duck) - keeping calls active")
|
|
115
|
-
hasAudioFocus = false
|
|
116
|
-
// Don't hold calls for ducking scenarios in self-managed calls
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
82
|
// --- INITIALIZATION ---
|
|
122
83
|
fun initialize(context: Context) {
|
|
123
84
|
synchronized(initializationLock) {
|
|
@@ -165,102 +126,6 @@ object CallEngine {
|
|
|
165
126
|
}
|
|
166
127
|
}
|
|
167
128
|
|
|
168
|
-
// --- Enhanced Audio Focus Management for Self-Managed Calls ---
|
|
169
|
-
private fun requestAudioFocus(): Boolean {
|
|
170
|
-
val context = requireContext()
|
|
171
|
-
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
172
|
-
|
|
173
|
-
if (hasAudioFocus) {
|
|
174
|
-
Log.d(TAG, "Audio focus already granted")
|
|
175
|
-
return true
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
Log.d(TAG, "Requesting audio focus for self-managed call (attempt ${audioFocusRetryCount + 1})")
|
|
179
|
-
|
|
180
|
-
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
181
|
-
requestAudioFocusApi26Plus()
|
|
182
|
-
} else {
|
|
183
|
-
requestAudioFocusLegacy()
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
val success = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
|
187
|
-
hasAudioFocus = success
|
|
188
|
-
|
|
189
|
-
Log.d(TAG, "Audio focus request result: $result (granted: $success)")
|
|
190
|
-
|
|
191
|
-
if (!success && audioFocusRetryCount < MAX_AUDIO_FOCUS_RETRIES) {
|
|
192
|
-
// Retry after a short delay
|
|
193
|
-
audioFocusRetryCount++
|
|
194
|
-
audioFocusRetryHandler.postDelayed({
|
|
195
|
-
Log.d(TAG, "Retrying audio focus request...")
|
|
196
|
-
requestAudioFocus()
|
|
197
|
-
}, 200)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return success
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
@RequiresApi(Build.VERSION_CODES.O)
|
|
204
|
-
private fun requestAudioFocusApi26Plus(): Int {
|
|
205
|
-
if (audioFocusRequest == null) {
|
|
206
|
-
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
|
|
207
|
-
.setAudioAttributes(
|
|
208
|
-
AudioAttributes.Builder()
|
|
209
|
-
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
|
|
210
|
-
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
|
211
|
-
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
|
|
212
|
-
.build()
|
|
213
|
-
)
|
|
214
|
-
.setOnAudioFocusChangeListener(audioFocusChangeListener)
|
|
215
|
-
.setAcceptsDelayedFocusGain(true)
|
|
216
|
-
.setWillPauseWhenDucked(false)
|
|
217
|
-
.build()
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return audioManager?.requestAudioFocus(audioFocusRequest!!) ?: AudioManager.AUDIOFOCUS_REQUEST_FAILED
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
@Suppress("DEPRECATION")
|
|
224
|
-
private fun requestAudioFocusLegacy(): Int {
|
|
225
|
-
return audioManager?.requestAudioFocus(
|
|
226
|
-
audioFocusChangeListener,
|
|
227
|
-
AudioManager.STREAM_VOICE_CALL,
|
|
228
|
-
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
|
|
229
|
-
) ?: AudioManager.AUDIOFOCUS_REQUEST_FAILED
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private fun abandonAudioFocus() {
|
|
233
|
-
if (!hasAudioFocus) return
|
|
234
|
-
|
|
235
|
-
audioManager?.let { am ->
|
|
236
|
-
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
237
|
-
audioFocusRequest?.let { request ->
|
|
238
|
-
am.abandonAudioFocusRequest(request)
|
|
239
|
-
} ?: AudioManager.AUDIOFOCUS_REQUEST_FAILED
|
|
240
|
-
} else {
|
|
241
|
-
@Suppress("DEPRECATION")
|
|
242
|
-
am.abandonAudioFocus(audioFocusChangeListener)
|
|
243
|
-
}
|
|
244
|
-
Log.d(TAG, "Audio focus abandoned, result: $result")
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
hasAudioFocus = false
|
|
248
|
-
audioFocusRetryCount = 0
|
|
249
|
-
audioFocusRetryHandler.removeCallbacksAndMessages(null)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private fun holdAllActiveCalls(heldBySystem: Boolean) {
|
|
253
|
-
activeCalls.values.filter { it.state == CallState.ACTIVE }.forEach { call ->
|
|
254
|
-
holdCallInternal(call.callId, heldBySystem = heldBySystem)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private fun resumeSystemHeldCalls() {
|
|
259
|
-
activeCalls.values.filter { it.state == CallState.HELD && it.wasHeldBySystem }.forEach { call ->
|
|
260
|
-
unholdCallInternal(call.callId, resumedBySystem = true)
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
129
|
// --- Lock Screen Bypass Management ---
|
|
265
130
|
fun registerLockScreenBypassCallback(callback: LockScreenBypassCallback) {
|
|
266
131
|
lockScreenBypassCallbacks.add(callback)
|
|
@@ -387,7 +252,7 @@ object CallEngine {
|
|
|
387
252
|
updateLockScreenBypass()
|
|
388
253
|
}
|
|
389
254
|
|
|
390
|
-
// ---
|
|
255
|
+
// --- Outgoing Call Management ---
|
|
391
256
|
fun startOutgoingCall(
|
|
392
257
|
callId: String,
|
|
393
258
|
callType: String,
|
|
@@ -419,10 +284,8 @@ object CallEngine {
|
|
|
419
284
|
currentCallId = callId
|
|
420
285
|
Log.d(TAG, "Call $callId added to activeCalls. State: DIALING")
|
|
421
286
|
|
|
422
|
-
//
|
|
287
|
+
// ONLY set audio mode - let system handle audio focus for self-managed calls
|
|
423
288
|
setAudioMode()
|
|
424
|
-
val audioFocusGranted = requestAudioFocus()
|
|
425
|
-
Log.d(TAG, "Audio focus for outgoing call: $audioFocusGranted")
|
|
426
289
|
|
|
427
290
|
registerPhoneAccount()
|
|
428
291
|
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
|
@@ -447,10 +310,8 @@ object CallEngine {
|
|
|
447
310
|
telecomManager.placeCall(addressUri, extras)
|
|
448
311
|
startForegroundService()
|
|
449
312
|
|
|
450
|
-
// Start ringback
|
|
451
|
-
|
|
452
|
-
startRingback()
|
|
453
|
-
}
|
|
313
|
+
// Start ringback (system will handle audio focus)
|
|
314
|
+
startRingback()
|
|
454
315
|
|
|
455
316
|
bringAppToForeground()
|
|
456
317
|
keepScreenAwake(true)
|
|
@@ -494,7 +355,6 @@ object CallEngine {
|
|
|
494
355
|
|
|
495
356
|
registerPhoneAccount()
|
|
496
357
|
setAudioMode()
|
|
497
|
-
requestAudioFocus()
|
|
498
358
|
bringAppToForeground()
|
|
499
359
|
startForegroundService()
|
|
500
360
|
keepScreenAwake(true)
|
|
@@ -505,7 +365,7 @@ object CallEngine {
|
|
|
505
365
|
emitOutgoingCallAnsweredWithMetadata(callId)
|
|
506
366
|
}
|
|
507
367
|
|
|
508
|
-
// ---
|
|
368
|
+
// --- Call Answer Management (SIMPLIFIED - NO MANUAL AUDIO FOCUS) ---
|
|
509
369
|
fun callAnsweredFromJS(callId: String) {
|
|
510
370
|
Log.d(TAG, "callAnsweredFromJS: $callId - remote party answered")
|
|
511
371
|
coreCallAnswered(callId, isLocalAnswer = false)
|
|
@@ -516,7 +376,7 @@ object CallEngine {
|
|
|
516
376
|
coreCallAnswered(callId, isLocalAnswer = true)
|
|
517
377
|
}
|
|
518
378
|
|
|
519
|
-
//
|
|
379
|
+
// SIMPLIFIED: Let system handle audio focus for self-managed calls
|
|
520
380
|
private fun coreCallAnswered(callId: String, isLocalAnswer: Boolean) {
|
|
521
381
|
Log.d(TAG, "coreCallAnswered: $callId, isLocalAnswer: $isLocalAnswer")
|
|
522
382
|
|
|
@@ -526,19 +386,13 @@ object CallEngine {
|
|
|
526
386
|
return
|
|
527
387
|
}
|
|
528
388
|
|
|
529
|
-
// Set audio mode
|
|
389
|
+
// Set audio mode and let system handle audio focus
|
|
530
390
|
setAudioMode()
|
|
531
391
|
|
|
532
|
-
//
|
|
533
|
-
val audioFocusGranted = requestAudioFocus()
|
|
534
|
-
if (!audioFocusGranted) {
|
|
535
|
-
Log.w(TAG, "Audio focus not granted for call $callId, but proceeding anyway")
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Now set call to ACTIVE
|
|
392
|
+
// Set call to ACTIVE
|
|
539
393
|
activeCalls[callId] = callInfo.copy(state = CallState.ACTIVE)
|
|
540
394
|
currentCallId = callId
|
|
541
|
-
Log.d(TAG, "Call $callId set to ACTIVE state (audio focus
|
|
395
|
+
Log.d(TAG, "Call $callId set to ACTIVE state (system manages audio focus)")
|
|
542
396
|
|
|
543
397
|
// Clean up media and UI
|
|
544
398
|
stopRingtone()
|
|
@@ -660,11 +514,6 @@ object CallEngine {
|
|
|
660
514
|
return
|
|
661
515
|
}
|
|
662
516
|
|
|
663
|
-
// Request audio focus when resuming a call
|
|
664
|
-
if (resumedBySystem) {
|
|
665
|
-
requestAudioFocus()
|
|
666
|
-
}
|
|
667
|
-
|
|
668
517
|
activeCalls[callId] = callInfo.copy(
|
|
669
518
|
state = CallState.ACTIVE,
|
|
670
519
|
wasHeldBySystem = false
|
|
@@ -780,7 +629,7 @@ object CallEngine {
|
|
|
780
629
|
})
|
|
781
630
|
}
|
|
782
631
|
|
|
783
|
-
// --- Audio Management ---
|
|
632
|
+
// --- Audio Management (SIMPLIFIED - NO MANUAL AUDIO FOCUS) ---
|
|
784
633
|
fun getAudioDevices(): AudioRoutesInfo {
|
|
785
634
|
val context = requireContext()
|
|
786
635
|
audioManager = audioManager ?: context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: run {
|
|
@@ -886,6 +735,7 @@ object CallEngine {
|
|
|
886
735
|
|
|
887
736
|
private fun setAudioMode() {
|
|
888
737
|
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
|
|
738
|
+
Log.d(TAG, "Audio mode set to MODE_IN_COMMUNICATION (system handles audio focus)")
|
|
889
739
|
}
|
|
890
740
|
|
|
891
741
|
private fun resetAudioMode() {
|
|
@@ -894,7 +744,7 @@ object CallEngine {
|
|
|
894
744
|
audioManager?.stopBluetoothSco()
|
|
895
745
|
audioManager?.isBluetoothScoOn = false
|
|
896
746
|
audioManager?.isSpeakerphoneOn = false
|
|
897
|
-
|
|
747
|
+
Log.d(TAG, "Audio mode reset to MODE_NORMAL")
|
|
898
748
|
}
|
|
899
749
|
}
|
|
900
750
|
|