Skip to content

Commit 799e1ec

Browse files
committed
feat: added android <26 handling
1 parent d6e4585 commit 799e1ec

File tree

6 files changed

+227
-50
lines changed

6 files changed

+227
-50
lines changed

packages/react-native-callingx/android/src/main/java/com/callingx/CallService.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.callingx
1818

19+
import CallRepository
1920
import android.app.Service
2021
import android.content.Intent
2122
import android.net.Uri
@@ -48,7 +49,6 @@ import kotlinx.coroutines.launch
4849
* calls can consume significant memory, although that would require more complex setup to make it
4950
* work across multiple process.
5051
*/
51-
@RequiresApi(Build.VERSION_CODES.O)
5252
class CallService : Service(), CallRepository.Listener {
5353

5454
companion object {
@@ -80,7 +80,7 @@ class CallService : Service(), CallRepository.Listener {
8080

8181
private lateinit var headlessJSManager: HeadlessTaskManager
8282
private lateinit var notificationManager: CallNotificationManager
83-
private lateinit var telecomRepository: CallRepository
83+
private lateinit var callRepository: CallRepository
8484

8585
private val binder = CallServiceBinder()
8686
private val scope: CoroutineScope = CoroutineScope(SupervisorJob())
@@ -93,8 +93,8 @@ class CallService : Service(), CallRepository.Listener {
9393

9494
notificationManager = CallNotificationManager(applicationContext)
9595
headlessJSManager = HeadlessTaskManager(applicationContext)
96-
telecomRepository = CallRepository(applicationContext)
97-
telecomRepository.setListener(this)
96+
callRepository = CallRepositoryFactory.create(applicationContext)
97+
callRepository.setListener(this)
9898

9999
sendBroadcastEvent(CallingxModule.SERVICE_READY_ACTION)
100100
}
@@ -105,7 +105,7 @@ class CallService : Service(), CallRepository.Listener {
105105

106106
notificationManager.cancelNotifications()
107107
notificationManager.stopRingtone()
108-
telecomRepository.release()
108+
callRepository.release()
109109
headlessJSManager.release()
110110

111111
if (isInForeground) {
@@ -272,7 +272,7 @@ class CallService : Service(), CallRepository.Listener {
272272

273273
public fun processAction(action: CallAction) {
274274
Log.d(TAG, "[service] processAction: Processing action: ${action::class.simpleName}")
275-
val currentCall = telecomRepository.currentCall.value
275+
val currentCall = callRepository.currentCall.value
276276
if (currentCall is Call.Registered) {
277277
currentCall.processAction(action)
278278
} else {
@@ -297,7 +297,7 @@ class CallService : Service(), CallRepository.Listener {
297297
Log.d(TAG, "[service] registerCall: ${if (incoming) "in" else "out"} call")
298298

299299
// If we have an ongoing call ignore command
300-
if (telecomRepository.currentCall.value is Call.Registered) {
300+
if (callRepository.currentCall.value is Call.Registered) {
301301
Log.w(TAG, "[service] registerCall: Call already registered, ignoring new call request")
302302
return
303303
}
@@ -316,7 +316,7 @@ class CallService : Service(), CallRepository.Listener {
316316
Log.d(TAG, "[service] registerCall: Call details - Name: $name, URI: $uri")
317317

318318
scope.launch {
319-
telecomRepository.registerCall(
319+
callRepository.registerCall(
320320
callId,
321321
name,
322322
uri,

packages/react-native-callingx/android/src/main/java/com/callingx/CallingxModule.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import android.content.Context
66
import android.content.Intent
77
import android.content.IntentFilter
88
import android.content.ServiceConnection
9-
import android.net.Uri
109
import android.os.Build
1110
import android.os.Bundle
1211
import android.os.IBinder
1312
import android.telecom.DisconnectCause
1413
import android.util.Log
14+
import androidx.core.content.ContextCompat
1515
import com.callingx.model.CallAction
1616
import com.callingx.notifications.NotificationChannelsManager
1717
import com.callingx.notifications.NotificationsConfig
@@ -25,6 +25,7 @@ import com.facebook.react.bridge.WritableMap
2525
import com.facebook.react.bridge.WritableNativeArray
2626
import com.facebook.react.module.annotations.ReactModule
2727
import com.facebook.react.modules.core.DeviceEventManagerModule
28+
import androidx.core.net.toUri
2829

2930
@ReactModule(name = CallingxModule.NAME)
3031
class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec(reactContext) {
@@ -314,11 +315,11 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
314315
this.action = action
315316
putExtra(CallService.EXTRA_CALL_ID, callId)
316317
putExtra(CallService.EXTRA_NAME, callerName)
317-
putExtra(CallService.EXTRA_URI, Uri.parse(phoneNumber))
318+
putExtra(CallService.EXTRA_URI, phoneNumber.toUri())
318319
putExtra(CallService.EXTRA_IS_VIDEO, hasVideo)
319320
putExtra(CallService.EXTRA_DISPLAY_OPTIONS, Arguments.toBundle(displayOptions))
320321
}
321-
.also { reactApplicationContext.startForegroundService(it) }
322+
.also { ContextCompat.startForegroundService(reactApplicationContext, it) }
322323
}
323324

324325
private fun executeServiceAction(action: CallAction, promise: Promise) {

packages/react-native-callingx/android/src/main/java/com/callingx/notifications/CallNotificationManager.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import android.media.RingtoneManager
77
import android.os.Build
88
import android.telecom.DisconnectCause
99
import android.util.Log
10-
import androidx.annotation.RequiresApi
1110
import androidx.core.app.NotificationCompat
1211
import androidx.core.app.NotificationManagerCompat
1312
import androidx.core.app.Person
@@ -24,7 +23,6 @@ import com.callingx.model.Call
2423
*
2524
* @see updateCallNotification
2625
*/
27-
@RequiresApi(Build.VERSION_CODES.O)
2826
class CallNotificationManager(
2927
private val context: Context,
3028
private val notificationManager: NotificationManagerCompat =
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import android.content.Context
2+
import android.net.Uri
3+
import android.os.Build
4+
import android.os.Bundle
5+
import android.telecom.DisconnectCause
6+
import com.callingx.model.Call
7+
import com.callingx.repo.TelecomCallRepository
8+
import kotlinx.coroutines.flow.StateFlow
9+
10+
interface CallRepository {
11+
12+
interface Listener {
13+
fun onCallStateChanged(call: Call)
14+
fun onIsCallAnswered(callId: String)
15+
fun onIsCallDisconnected(cause: DisconnectCause)
16+
fun onIsCallInactive(callId: String)
17+
fun onIsCallActive(callId: String)
18+
fun onCallRegistered(callId: String)
19+
fun onMuteCallChanged(callId: String, isMuted: Boolean)
20+
fun onCallEndpointChanged(callId: String, endpoint: String)
21+
}
22+
23+
val currentCall: StateFlow<Call>
24+
25+
fun setListener(listener: Listener)
26+
fun release()
27+
28+
suspend fun registerCall(
29+
callId: String,
30+
displayName: String,
31+
address: Uri,
32+
isIncoming: Boolean,
33+
isVideo: Boolean,
34+
displayOptions: Bundle?,
35+
)
36+
}
37+
38+
object CallRepositoryFactory {
39+
40+
fun create(context: Context): CallRepository {
41+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
42+
TelecomCallRepository(context) // Your current CallRepository renamed
43+
} else {
44+
LegacyCallRepository(context) // Fallback implementation
45+
}
46+
}
47+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import android.content.Context
2+
import android.net.Uri
3+
import android.os.Bundle
4+
import android.util.Log
5+
import androidx.core.telecom.CallAttributesCompat
6+
import com.callingx.model.Call
7+
import com.callingx.model.CallAction
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.SupervisorJob
11+
import kotlinx.coroutines.cancel
12+
import kotlinx.coroutines.channels.Channel
13+
import kotlinx.coroutines.flow.MutableStateFlow
14+
import kotlinx.coroutines.flow.asStateFlow
15+
import kotlinx.coroutines.flow.consumeAsFlow
16+
import kotlinx.coroutines.flow.update
17+
import kotlinx.coroutines.launch
18+
19+
class LegacyCallRepository(private val context: Context) : CallRepository {
20+
21+
companion object {
22+
private const val TAG = "[Callingx] LegacyCallRepository"
23+
}
24+
25+
private val _currentCall: MutableStateFlow<Call> = MutableStateFlow(Call.None)
26+
override val currentCall = _currentCall.asStateFlow()
27+
28+
private var listener: CallRepository.Listener? = null
29+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
30+
31+
override fun setListener(listener: CallRepository.Listener) {
32+
this.listener = listener
33+
// Observe call state changes
34+
scope.launch { currentCall.collect { listener.onCallStateChanged(it) } }
35+
}
36+
37+
override fun release() {
38+
_currentCall.value = Call.None
39+
40+
listener = null
41+
42+
scope.cancel()
43+
}
44+
45+
override suspend fun registerCall(
46+
callId: String,
47+
displayName: String,
48+
address: Uri,
49+
isIncoming: Boolean,
50+
isVideo: Boolean,
51+
displayOptions: Bundle?,
52+
) {
53+
54+
val attributes = createCallAttributes(displayName, address, isIncoming, isVideo)
55+
val actionSource = Channel<CallAction>()
56+
57+
_currentCall.value =
58+
Call.Registered(
59+
id = callId,
60+
isActive = false,
61+
isOnHold = false,
62+
callAttributes = attributes,
63+
displayOptions = displayOptions,
64+
isMuted = false,
65+
errorCode = null,
66+
currentCallEndpoint = null,
67+
availableCallEndpoints = emptyList(),
68+
actionSource = actionSource,
69+
)
70+
71+
listener?.onCallRegistered(callId)
72+
73+
// Process actions without telecom SDK
74+
scope.launch {
75+
actionSource.consumeAsFlow().collect { action -> processActionLegacy(action) }
76+
}
77+
}
78+
79+
private fun processActionLegacy(action: CallAction) {
80+
when (action) {
81+
is CallAction.Answer -> {
82+
updateCurrentCall { copy(isActive = true, isOnHold = false) }
83+
(currentCall.value as? Call.Registered)?.let { listener?.onIsCallAnswered(it.id) }
84+
}
85+
is CallAction.Disconnect -> {
86+
val call = currentCall.value as? Call.Registered
87+
if (call != null) {
88+
_currentCall.value =
89+
Call.Unregistered(call.id, call.callAttributes, action.cause)
90+
listener?.onIsCallDisconnected(action.cause)
91+
}
92+
}
93+
is CallAction.ToggleMute -> {
94+
updateCurrentCall { copy(isMuted = action.isMute) }
95+
}
96+
// Handle other actions...
97+
else -> {
98+
/* No-op for unsupported actions */
99+
}
100+
}
101+
}
102+
103+
private fun updateCurrentCall(transform: Call.Registered.() -> Call) {
104+
val currentState = _currentCall.value
105+
Log.d(
106+
TAG,
107+
"[repository] updateCurrentCall: Current call state: ${currentState::class.simpleName}"
108+
)
109+
110+
_currentCall.update { call ->
111+
if (call is Call.Registered) {
112+
val updated = call.transform()
113+
Log.d(
114+
TAG,
115+
"[repository] updateCurrentCall: Call state updated to: ${updated::class.simpleName}"
116+
)
117+
updated
118+
} else {
119+
Log.w(
120+
TAG,
121+
"[repository] updateCurrentCall: Call is not Registered, skipping update"
122+
)
123+
call
124+
}
125+
}
126+
}
127+
128+
private fun createCallAttributes(
129+
displayName: String,
130+
address: Uri,
131+
isIncoming: Boolean,
132+
isVideo: Boolean
133+
): CallAttributesCompat {
134+
Log.d(
135+
TAG,
136+
"createCallAttributes: Creating CallAttributes - Direction: ${if (isIncoming) "Incoming" else "Outgoing"}, Type: ${if (isVideo) "Video" else "Audio"}"
137+
)
138+
return CallAttributesCompat(
139+
displayName = displayName,
140+
address = address,
141+
direction =
142+
if (isIncoming) {
143+
CallAttributesCompat.DIRECTION_INCOMING
144+
} else {
145+
CallAttributesCompat.DIRECTION_OUTGOING
146+
},
147+
callType =
148+
if (isVideo) {
149+
CallAttributesCompat.CALL_TYPE_VIDEO_CALL
150+
} else {
151+
CallAttributesCompat.CALL_TYPE_AUDIO_CALL
152+
},
153+
callCapabilities =
154+
CallAttributesCompat.SUPPORTS_SET_INACTIVE or
155+
CallAttributesCompat.SUPPORTS_STREAM or
156+
CallAttributesCompat.SUPPORTS_TRANSFER,
157+
)
158+
}
159+
}

0 commit comments

Comments
 (0)