@qusaieilouti99/call-manager 0.1.159 → 0.1.161

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.
@@ -389,9 +389,7 @@ object CallEngine {
389
389
  updateLockScreenBypass()
390
390
  setAudioMode()
391
391
  registerAudioDeviceCallback()
392
- mainHandler.postDelayed({
393
- setInitialAudioRoute(callInfo.callType)
394
- }, 500L)
392
+ // The initial audio route will be set automatically by the onStateChanged(STATE_ACTIVE) callback
395
393
  if (isLocalAnswer) {
396
394
  emitCallAnsweredWithMetadata(callId)
397
395
  } else {
@@ -489,7 +487,6 @@ object CallEngine {
489
487
  /**
490
488
  * FIX: This is the correct way to handle mute requests from the app's UI.
491
489
  * It uses the AudioManager, which is the source of truth for microphone state.
492
- * There is no `setMuted` method on the Connection object.
493
490
  */
494
491
  fun setMuted(callId: String, muted: Boolean) {
495
492
  if (!activeCalls.containsKey(callId)) {
@@ -513,7 +510,9 @@ object CallEngine {
513
510
  return
514
511
  }
515
512
  // Ensure our audio manager state is consistent with the system's request
516
- audioManager?.isMicrophoneMute = isMuted
513
+ if (audioManager?.isMicrophoneMute != isMuted) {
514
+ audioManager?.isMicrophoneMute = isMuted
515
+ }
517
516
  Log.d(TAG, "Telecom reported mute state for $callId is now: $isMuted. Emitting event.")
518
517
  val eventType = if (isMuted) CallEventType.CALL_MUTED else CallEventType.CALL_UNMUTED
519
518
  emitEvent(eventType, JSONObject().put("callId", callId))
@@ -675,8 +674,11 @@ object CallEngine {
675
674
  }
676
675
  }
677
676
 
678
- private fun setInitialAudioRoute(callType: String) {
679
- Log.d(TAG, "Determining initial audio route for call type: $callType")
677
+ /**
678
+ * FIX: This is now called from MyConnection when the call becomes active.
679
+ */
680
+ fun setInitialAudioRouteForCall(callId: String, callType: String) {
681
+ Log.d(TAG, "Setting initial audio route for call $callId, type: $callType")
680
682
  val defaultRoute = when {
681
683
  isBluetoothDeviceConnected() -> "Bluetooth"
682
684
  isWiredHeadsetConnected() -> "Headset"
@@ -1,12 +1,18 @@
1
1
  package com.margelo.nitro.qusaieilouti99.callmanager
2
2
 
3
3
  import android.content.Context
4
+ import android.os.Build
5
+ import android.os.OutcomeReceiver
4
6
  import android.telecom.CallAudioState
7
+ import android.telecom.CallEndpoint
8
+ import android.telecom.CallEndpointException
5
9
  import android.telecom.Connection
6
10
  import android.telecom.DisconnectCause
7
11
  import android.telecom.VideoProfile
8
12
  import android.util.Log
13
+ import androidx.annotation.RequiresApi
9
14
  import org.json.JSONObject
15
+ import java.util.concurrent.Executor
10
16
 
11
17
  class MyConnection(
12
18
  private val context: Context,
@@ -21,6 +27,8 @@ class MyConnection(
21
27
  }
22
28
 
23
29
  private var lastAudioState: CallAudioState? = null
30
+ // FIX: We must store the list of available endpoints when the system gives it to us.
31
+ private var availableCallEndpoints: List<CallEndpoint> = emptyList()
24
32
 
25
33
  init {
26
34
  connectionProperties = PROPERTY_SELF_MANAGED
@@ -41,6 +49,14 @@ class MyConnection(
41
49
  Log.d(TAG, "MyConnection for callId $callId created and added to CallEngine. Type: $callType")
42
50
  }
43
51
 
52
+ // FIX: Override the callback to receive endpoint updates from the system.
53
+ @RequiresApi(Build.VERSION_CODES.P)
54
+ override fun onAvailableCallEndpointsChanged(endpoints: List<CallEndpoint>) {
55
+ super.onAvailableCallEndpointsChanged(endpoints)
56
+ Log.d(TAG, "Available call endpoints changed. Count: ${endpoints.size}")
57
+ this.availableCallEndpoints = endpoints
58
+ }
59
+
44
60
  override fun onSilence() {
45
61
  super.onSilence()
46
62
  Log.d(TAG, "onSilence called by system for callId: $callId. Silencing ringtone.")
@@ -103,42 +119,57 @@ class MyConnection(
103
119
  )
104
120
  }
105
121
 
106
- override fun onStopDtmfTone() {
107
- super.onStopDtmfTone()
108
- Log.d(TAG, "Stopping DTMF tone for callId: $callId")
122
+ override fun onStateChanged(state: Int) {
123
+ super.onStateChanged(state)
124
+ if (state == Connection.STATE_ACTIVE) {
125
+ CallEngine.setInitialAudioRouteForCall(callId, callType)
126
+ } else if (state == Connection.STATE_DISCONNECTED) {
127
+ CallEngine.removeTelecomConnection(callId)
128
+ }
109
129
  }
110
130
 
111
- override fun onShowIncomingCallUi() {
112
- super.onShowIncomingCallUi()
113
- Log.d(TAG, "onShowIncomingCallUi for callId: $callId")
131
+ fun requestAudioRoute(route: Int) {
132
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
133
+ requestCallEndpointChange(route)
134
+ } else {
135
+ Log.d(TAG, "Using deprecated setAudioRoute for API < 34")
136
+ @Suppress("DEPRECATION")
137
+ setAudioRoute(route)
138
+ }
114
139
  }
115
140
 
116
- override fun onStateChanged(state: Int) {
117
- super.onStateChanged(state)
118
- val stateString = when (state) {
119
- STATE_INITIALIZING -> "INITIALIZING"
120
- STATE_NEW -> "NEW"
121
- STATE_RINGING -> "RINGING"
122
- STATE_DIALING -> "DIALING"
123
- STATE_ACTIVE -> "ACTIVE"
124
- STATE_HOLDING -> "HOLDING"
125
- STATE_DISCONNECTED -> "DISCONNECTED"
126
- else -> "UNKNOWN"
141
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
142
+ private fun requestCallEndpointChange(route: Int) {
143
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
144
+ return // Should not happen if we are on UDC+, but a good safeguard.
127
145
  }
128
- Log.d(TAG, "Connection state changed for callId: $callId. New state: $stateString ($state)")
129
146
 
130
- if (state == STATE_DISCONNECTED) {
131
- CallEngine.removeTelecomConnection(callId)
147
+ val endpointType = when (route) {
148
+ CallAudioState.ROUTE_EARPIECE -> CallEndpoint.TYPE_EARPIECE
149
+ CallAudioState.ROUTE_SPEAKER -> CallEndpoint.TYPE_SPEAKER
150
+ CallAudioState.ROUTE_BLUETOOTH -> CallEndpoint.TYPE_BLUETOOTH
151
+ CallAudioState.ROUTE_WIRED_HEADSET -> CallEndpoint.TYPE_WIRED_HEADSET
152
+ else -> {
153
+ Log.w(TAG, "Unsupported route requested: $route")
154
+ return
155
+ }
132
156
  }
133
- }
134
157
 
135
- /**
136
- * Requests a change to the audio route using the deprecated `setAudioRoute` method.
137
- * This is guaranteed to be available in your build environment.
138
- */
139
- fun requestAudioRoute(route: Int) {
140
- Log.d(TAG, "Requesting audio route change to $route using deprecated setAudioRoute.")
141
- @Suppress("DEPRECATION")
142
- setAudioRoute(route)
158
+ // FIX: Use the locally stored list that the system gave us.
159
+ val targetEndpoint = this.availableCallEndpoints.find { it.type == endpointType }
160
+
161
+ if (targetEndpoint != null) {
162
+ Log.d(TAG, "Requesting CallEndpoint change to: ${targetEndpoint.name} (Type: $endpointType)")
163
+ requestCallEndpointChange(targetEndpoint, context.mainExecutor, object : OutcomeReceiver<Void, CallEndpointException> {
164
+ override fun onResult(result: Void?) {
165
+ Log.d(TAG, "CallEndpoint change request successful.")
166
+ }
167
+ override fun onError(error: CallEndpointException) {
168
+ Log.e(TAG, "CallEndpoint change request failed.", error)
169
+ }
170
+ })
171
+ } else {
172
+ Log.w(TAG, "Could not find an available CallEndpoint for type: $endpointType")
173
+ }
143
174
  }
144
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.159",
3
+ "version": "0.1.161",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",