From 24568b341facbfdc80db2ae70bdf5877fa071992 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 2 Oct 2025 14:52:19 +0530 Subject: [PATCH 01/29] phase 1: code refactor - non telecom related --- gradle/libs.versions.toml | 2 + stream-video-android-core/build.gradle.kts | 3 + .../video/android/core/ClientState.kt | 48 +-- .../video/android/core/StreamVideoClient.kt | 11 +- .../DefaultNotificationHandler.kt | 20 +- .../StreamDefaultNotificationHandler.kt | 18 +- .../receivers/RejectCallBroadcastReceiver.kt | 5 +- .../internal/service/CallService.kt | 79 +--- .../internal/service/IncomingCallPresenter.kt | 117 ++++++ .../internal/service/ServiceIntentBuilder.kt | 105 ++++++ .../internal/service/ServiceLauncher.kt | 342 ++++++++++++++++++ .../service/ServiceNotificationRetriever.kt | 114 ++++++ .../internal/service/StartServiceParam.kt | 23 ++ 13 files changed, 783 insertions(+), 104 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d85188ff1c4..84c05722b02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -225,6 +225,8 @@ play-services-mlkit-barcode-scanning = { group = "com.google.android.gms", name androidx-camera-view = { group = "androidx.camera", name = "camera-view", version = "1.3.0" } androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version = "1.3.0" } zxing-core = { group = "com.google.zxing", name = "core", version = "3.5.2" } +#jetpack telecom +androidx-telecom = { group = "androidx.core", name = "core-telecom", version = "1.0.0" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/stream-video-android-core/build.gradle.kts b/stream-video-android-core/build.gradle.kts index 6a0d0b6d0c2..6e812528d63 100644 --- a/stream-video-android-core/build.gradle.kts +++ b/stream-video-android-core/build.gradle.kts @@ -185,6 +185,9 @@ dependencies { implementation(libs.stream.push.delegate) api(libs.stream.push.permissions) + //jetpack telecom + implementation(libs.androidx.telecom) + // datastore api(libs.androidx.datastore) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 065183f222e..4ab7c794ca9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -26,6 +26,8 @@ import io.getstream.android.video.generated.models.VideoEvent import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.notifications.internal.service.CallService +import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher +import io.getstream.video.android.core.notifications.internal.service.StopForegroundServiceSource import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.utils.safeCallWithDefault import io.getstream.video.android.model.StreamCallId @@ -85,6 +87,7 @@ class ClientState(private val client: StreamVideo) { public val activeCall: StateFlow = _activeCall public val callConfigRegistry = (client as StreamVideoClient).callServiceConfigRegistry + val serviceLauncher = ServiceLauncher(client.context) /** * Returns true if there is an active or ringing call @@ -216,17 +219,20 @@ class ClientState(private val client: StreamVideo) { * This depends on the flag in [StreamVideoBuilder] called `runForegroundServiceForCalls` */ internal fun maybeStartForegroundService(call: Call, trigger: String) { - val callConfig = streamVideoClient.callServiceConfigRegistry.get(call.type) - if (callConfig.runCallServiceInForeground) { - val context = streamVideoClient.context - val serviceIntent = CallService.buildStartIntent( - context, - StreamCallId.fromCallCid(call.cid), + when (trigger) { + CallService.TRIGGER_ONGOING_CALL -> serviceLauncher.showOnGoingCall( + call, + trigger, + streamVideoClient, + ) + + CallService.TRIGGER_OUTGOING_CALL -> serviceLauncher.showOutgoingCall( + call, trigger, - "maybeStartForegroundService, trigger: $trigger", - callServiceConfiguration = callConfig, + streamVideoClient, ) - ContextCompat.startForegroundService(context, serviceIntent) + + else -> {} } } @@ -238,29 +244,9 @@ class ClientState(private val client: StreamVideo) { if (callConfig.runCallServiceInForeground) { val context = streamVideoClient.context - val serviceIntent = CallService.buildStopIntent( - context, - call, - callConfig, - ) logger.d { "Building stop intent for call_id: ${call.cid}" } - serviceIntent.let { intent: Intent -> - val bundle = intent.extras - val keys = bundle?.keySet() - if (keys != null) { - val sb = StringBuilder() - for (key in keys) { - val itemInBundle = bundle[key] - val text = "key:$key, value=$itemInBundle" - sb.append(text) - sb.append("\n") - } - if (sb.toString().isNotEmpty()) { - logger.d { " [maybeStopForegroundService], stop intent extras: $sb" } - } - } - } - context.startService(serviceIntent) + val serviceLauncher = ServiceLauncher(context) + serviceLauncher.stopService(call) } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index 4f580bdf632..dd1f6e23fa3 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -98,6 +98,8 @@ import io.getstream.video.android.core.notifications.internal.StreamNotification import io.getstream.video.android.core.notifications.internal.service.ANY_MARKER import io.getstream.video.android.core.notifications.internal.service.CallService import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry +import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder +import io.getstream.video.android.core.notifications.internal.service.StopServiceParam import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.ErrorResponse @@ -219,12 +221,15 @@ internal class StreamVideoClient internal constructor( val callConfig = callServiceConfigRegistry.get(activeCall?.type ?: ANY_MARKER) val runCallServiceInForeground = callConfig.runCallServiceInForeground if (runCallServiceInForeground) { + safeCall { - val serviceIntent = CallService.buildStopIntent( + val serviceIntent = ServiceIntentBuilder().buildStopIntent( context = context, - callServiceConfiguration = callConfig, + StopServiceParam(callServiceConfiguration = callConfig), ) - context.stopService(serviceIntent) + serviceIntent.let { + context.stopService(serviceIntent) + } } } activeCall?.leave() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt index 019990a0ce2..94d12010840 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt @@ -48,7 +48,7 @@ import io.getstream.video.android.core.notifications.NotificationHandler.Compani import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_NOTIFICATION import io.getstream.video.android.core.notifications.dispatchers.DefaultNotificationDispatcher import io.getstream.video.android.core.notifications.dispatchers.NotificationDispatcher -import io.getstream.video.android.core.notifications.internal.service.CallService +import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher import io.getstream.video.android.core.notifications.medianotifications.MediaNotificationConfig import io.getstream.video.android.core.notifications.medianotifications.MediaNotificationContent import io.getstream.video.android.core.notifications.medianotifications.MediaNotificationVisuals @@ -88,6 +88,8 @@ public open class DefaultNotificationHandler( private val logger by taggedLogger("Call:NotificationHandler") val intentResolver = DefaultStreamIntentResolver(application, DefaultNotificationIntentBundleResolver()) + private val serviceLauncher = ServiceLauncher(application) + protected val notificationManager: NotificationManagerCompat by lazy { NotificationManagerCompat.from(application).also { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -111,11 +113,15 @@ public open class DefaultNotificationHandler( payload: Map, ) { logger.d { "[onRingingCall] #ringing; callId: ${callId.id}" } - CallService.showIncomingCall( + val streamVideo = StreamVideo.instance() + serviceLauncher.showIncomingCall( application, callId, callDisplayName, - StreamVideo.instance().state.callConfigRegistry.get(callId.type), + streamVideo.state.callConfigRegistry.get(callId.type), + isVideo = isVideoCall(callId, payload), + payload = payload, + streamVideo, notification = getRingingCallNotification( RingingState.Incoming(), callId, @@ -933,6 +939,14 @@ public open class DefaultNotificationHandler( ) } + internal fun isVideoCall(callId: StreamCallId, payload: Map): Boolean { + if (payload.containsKey("video")) { + return payload["video"] == true + } + val call = StreamVideo.instanceOrNull()?.call(callId.type, callId.id) + return call?.isVideoEnabled() == true + } + companion object { internal val PENDING_INTENT_FLAG: Int by lazy { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt index 3525f39cac7..018074957e2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt @@ -57,6 +57,7 @@ import io.getstream.video.android.core.notifications.dispatchers.DefaultNotifica import io.getstream.video.android.core.notifications.dispatchers.NotificationDispatcher import io.getstream.video.android.core.notifications.extractor.DefaultNotificationContentExtractor import io.getstream.video.android.core.notifications.internal.service.CallService +import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher import io.getstream.video.android.core.utils.isAppInForeground import io.getstream.video.android.core.utils.safeCall import io.getstream.video.android.model.StreamCallId @@ -146,6 +147,7 @@ constructor( NotificationPermissionHandler by notificationPermissionHandler { private val logger by taggedLogger("Video:StreamNotificationHandler") + private val serviceLauncher = ServiceLauncher(application) // START REGION : On push arrived override fun onRingingCall( @@ -154,11 +156,15 @@ constructor( payload: Map, ) { logger.d { "[onRingingCall] #ringing; callId: ${callId.id}" } - CallService.showIncomingCall( + val streamVideo = StreamVideo.instance() + serviceLauncher.showIncomingCall( application, callId, callDisplayName, - StreamVideo.instance().state.callConfigRegistry.get(callId.type), + streamVideo.state.callConfigRegistry.get(callId.type), + isVideo = isVideoCall(callId, payload), + payload = payload, + streamVideo, notification = getRingingCallNotification( RingingState.Incoming(), callId, @@ -1149,4 +1155,12 @@ constructor( internal fun clearMediaSession(callId: StreamCallId?) = safeCall { callId?.let { mediaSessionController.clear(it) } } + + internal fun isVideoCall(callId: StreamCallId, payload: Map): Boolean { + if (payload.containsKey("video")) { + return payload["video"] == true + } + val call = StreamVideo.instanceOrNull()?.call(callId.type, callId.id) + return call?.isVideoEnabled() == true + } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt index ef4b8bedb03..912d554f70a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt @@ -26,6 +26,7 @@ import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_REJECT_CALL import io.getstream.video.android.core.notifications.internal.service.CallService +import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher import io.getstream.video.android.model.StreamCallId /** @@ -54,7 +55,9 @@ internal class RejectCallBroadcastReceiver : GenericCallActionBroadcastReceiver( } } logger.d { "[onReceive] #ringing; callId: ${call.id}, action: ${intent.action}" } - CallService.removeIncomingCall( + + val serviceLauncher = ServiceLauncher(context) + serviceLauncher.removeIncomingCall( context, StreamCallId.fromCallCid(call.cid), StreamVideo.instance().state.callConfigRegistry.get(call.type), diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index 336b0c7ecf2..5af8f3b4396 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -96,6 +96,7 @@ internal open class CallService : Service() { // Call sounds private var callSoundPlayer: CallSoundPlayer? = null + private val serviceNotificationRetriever = ServiceNotificationRetriever() internal companion object { private const val TAG = "CallServiceCompanion" @@ -115,13 +116,16 @@ internal open class CallService : Service() { * @param trigger one of [TRIGGER_INCOMING_CALL], [TRIGGER_OUTGOING_CALL] or [TRIGGER_ONGOING_CALL] * @param callDisplayName the display name. */ + @Deprecated("",level = DeprecationLevel.WARNING) fun buildStartIntent( context: Context, callId: StreamCallId, trigger: String, callDisplayName: String? = null, callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, - ): Intent { + ): Intent + { + if(true) throw RuntimeException("Kyun aagaya idhar?") val serviceClass = callServiceConfiguration.serviceClass StreamLog.i(TAG) { "Resolved service class: $serviceClass" } val serviceIntent = Intent(context, serviceClass) @@ -160,6 +164,7 @@ internal open class CallService : Service() { * * @param context the context. */ + @Deprecated("",level = DeprecationLevel.ERROR) fun buildStopIntent( context: Context, call: Call? = null, @@ -180,6 +185,7 @@ internal open class CallService : Service() { intent.putExtra(EXTRA_STOP_SERVICE, true) } + @Deprecated("",level = DeprecationLevel.ERROR) fun showIncomingCall( context: Context, callId: StreamCallId, @@ -247,6 +253,7 @@ internal open class CallService : Service() { } } + @Deprecated("",level = DeprecationLevel.ERROR) fun removeIncomingCall( context: Context, callId: StreamCallId, @@ -492,69 +499,13 @@ internal open class CallService : Service() { streamCallId: StreamCallId, intentCallDisplayName: String?, ): Pair { - logger.d { - "[getNotificationPair] trigger: $trigger, callId: ${streamCallId.id}, callDisplayName: $intentCallDisplayName" - } - val notificationData: Pair = when (trigger) { - TRIGGER_ONGOING_CALL -> { - logger.d { "[getNotificationPair] Creating ongoing call notification" } - Pair( - first = streamVideo.getOngoingCallNotification( - callId = streamCallId, - callDisplayName = intentCallDisplayName, - payload = emptyMap(), - ), - second = streamCallId.hashCode(), - ) - } - - TRIGGER_INCOMING_CALL -> { - logger.d { "[getNotificationPair] Creating incoming call notification" } - val shouldHaveContentIntent = streamVideo.state.activeCall.value == null - logger.d { "[getNotificationPair] shouldHaveContentIntent: $shouldHaveContentIntent" } - Pair( - first = streamVideo.getRingingCallNotification( - ringingState = RingingState.Incoming(), - callId = streamCallId, - callDisplayName = intentCallDisplayName, - shouldHaveContentIntent = shouldHaveContentIntent, - payload = emptyMap(), - ), - second = streamCallId.getNotificationId(NotificationType.Incoming), - ) - } - - TRIGGER_OUTGOING_CALL -> { - logger.d { "[getNotificationPair] Creating outgoing call notification" } - Pair( - first = streamVideo.getRingingCallNotification( - ringingState = RingingState.Outgoing(), - callId = streamCallId, - callDisplayName = getString( - R.string.stream_video_outgoing_call_notification_title, - ), - payload = emptyMap(), - ), - second = streamCallId.getNotificationId( - NotificationType.Incoming, - ), // Same for incoming and outgoing - ) - } - - TRIGGER_REMOVE_INCOMING_CALL -> { - logger.d { "[getNotificationPair] Removing incoming call notification" } - Pair(null, streamCallId.getNotificationId(NotificationType.Incoming)) - } - - else -> { - logger.w { "[getNotificationPair] Unknown trigger: $trigger" } - Pair(null, streamCallId.hashCode()) - } - } - logger.d { - "[getNotificationPair] Generated notification: ${notificationData.first != null}, notificationId: ${notificationData.second}" - } - return notificationData + return serviceNotificationRetriever.getNotificationPair( + applicationContext, + trigger, + streamVideo, + streamCallId, + intentCallDisplayName, + ) } private fun maybePromoteToForegroundService( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt new file mode 100644 index 00000000000..e27fbec49c0 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.service + +import android.Manifest +import android.app.Notification +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.notifications.NotificationType +import io.getstream.video.android.core.notifications.internal.service.CallService +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig +import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder +import io.getstream.video.android.core.notifications.internal.service.StartServiceParam +import io.getstream.video.android.core.utils.safeCallWithResult +import io.getstream.video.android.model.StreamCallId + +class IncomingCallPresenter(private val serviceIntentBuilder: ServiceIntentBuilder) { + private val logger by taggedLogger("ServiceTriggers") + + fun showIncomingCall( + context: Context, + callId: StreamCallId, + callDisplayName: String?, + callServiceConfiguration: CallServiceConfig, + notification: Notification?, + ): ShowIncomingCallResult { + logger.d { + "[showIncomingCall] callId: ${callId.id}, callDisplayName: $callDisplayName, notification: ${notification != null}" + } + val hasActiveCall = StreamVideo.instanceOrNull()?.state?.activeCall?.value != null + logger.d { "[showIncomingCall] hasActiveCall: $hasActiveCall" } + var showIncomingCallResult = ShowIncomingCallResult.ERROR + safeCallWithResult { + if (!hasActiveCall) { + logger.d { "[showIncomingCall] Starting foreground service" } + ContextCompat.startForegroundService( + context, + serviceIntentBuilder.buildStartIntent( + context, + StartServiceParam( + callId, + TRIGGER_INCOMING_CALL, + callDisplayName, + callServiceConfiguration, + ), + ), + ) + ComponentName(context, CallService::class.java) + showIncomingCallResult = ShowIncomingCallResult.FG_SERVICE + } else { + logger.d { "[showIncomingCall] Starting regular service" } + context.startService( + serviceIntentBuilder.buildStartIntent( + context, + StartServiceParam( + callId, + TRIGGER_INCOMING_CALL, + callDisplayName, + callServiceConfiguration, + ), + ), + ) + showIncomingCallResult = ShowIncomingCallResult.SERVICE + } + }.onError { + // Show notification + logger.e { "Could not start service, showing notification only: $it" } + val hasPermission = ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS, + ) == PackageManager.PERMISSION_GRANTED + logger.i { "Has permission: $hasPermission" } + logger.i { "Notification: $notification" } + if (hasPermission && notification != null) { + logger.d { + "[showIncomingCall] Showing notification fallback with ID: ${callId.getNotificationId( + NotificationType.Incoming, + )}" + } + StreamVideo.instanceOrNull()?.getStreamNotificationDispatcher()?.notify( + callId, + callId.getNotificationId(NotificationType.Incoming), + notification, + ) + showIncomingCallResult = ShowIncomingCallResult.ONLY_NOTIFICATION + } else { + logger.w { + "[showIncomingCall] Cannot show notification - hasPermission: $hasPermission, notification: ${notification != null}" + } + } + } + return showIncomingCallResult + } +} + +enum class ShowIncomingCallResult { + FG_SERVICE, SERVICE, ONLY_NOTIFICATION, ERROR +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt new file mode 100644 index 00000000000..937355e10c6 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.service + +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import io.getstream.log.StreamLog +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_CID +import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_DISPLAY_NAME +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.EXTRA_STOP_SERVICE +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_KEY +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_OUTGOING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL +import io.getstream.video.android.core.utils.safeCallWithDefault +import io.getstream.video.android.model.StreamCallId + +class ServiceIntentBuilder { + + private val logger by taggedLogger("TelecomIntentBuilder") + + fun buildStartIntent(context: Context, startService: StartServiceParam): Intent { + val serviceClass = startService.callServiceConfiguration.serviceClass + logger.i { "Resolved service class: $serviceClass" } + val serviceIntent = Intent(context, serviceClass) + serviceIntent.putExtra(INTENT_EXTRA_CALL_CID, startService.callId) + + when (startService.trigger) { + TRIGGER_INCOMING_CALL -> { + serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_INCOMING_CALL) + serviceIntent.putExtra(INTENT_EXTRA_CALL_DISPLAY_NAME, startService.callDisplayName) + } + + TRIGGER_OUTGOING_CALL -> { + serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_OUTGOING_CALL) + } + + TRIGGER_ONGOING_CALL -> { + serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_ONGOING_CALL) + } + + TRIGGER_REMOVE_INCOMING_CALL -> { + serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_REMOVE_INCOMING_CALL) + } + + else -> { + throw IllegalArgumentException( + "Unknown ${startService.trigger}, must be one of: $TRIGGER_INCOMING_CALL, $TRIGGER_OUTGOING_CALL, $TRIGGER_ONGOING_CALL", + ) + } + } + return serviceIntent + } + + fun buildStopIntent(context: Context, stopServiceParam: StopServiceParam): Intent = + safeCallWithDefault(Intent(context, CallService::class.java)) { + val serviceClass = stopServiceParam.callServiceConfiguration.serviceClass + + val intent = if (isServiceRunning(context, serviceClass)) { + Intent(context, serviceClass) + } else { + Intent(context, CallService::class.java) + } + stopServiceParam.call?.let { call -> + logger.d { "[buildStopIntent], call_id:${call.cid}" } + val streamCallId = StreamCallId(call.type, call.id, call.cid) + intent.putExtra(INTENT_EXTRA_CALL_CID, streamCallId) + } + intent.putExtra(EXTRA_STOP_SERVICE, true) + } + + private fun isServiceRunning(context: Context, serviceClass: Class<*>): Boolean = + safeCallWithDefault(true) { + val activityManager = context.getSystemService( + Context.ACTIVITY_SERVICE, + ) as ActivityManager + val runningServices = activityManager.getRunningServices(Int.MAX_VALUE) + for (service in runningServices) { + if (serviceClass.name == service.service.className) { + logger.w { "Service is running: $serviceClass" } + return true + } + } + logger.w { "Service is NOT running: $serviceClass" } + return false + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt new file mode 100644 index 00000000000..96a08df1cc1 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -0,0 +1,342 @@ +package io.getstream.video.android.core.notifications.internal.service + +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.annotation.SuppressLint +import android.app.Notification +import android.content.Context +import android.os.Build +import android.os.Bundle +import android.telecom.DisconnectCause +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import androidx.core.telecom.CallsManager +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.NotificationType +import io.getstream.video.android.core.notifications.internal.VideoPushDelegate.Companion.DEFAULT_CALL_TEXT +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig +import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations +import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder +import io.getstream.video.android.core.notifications.internal.service.StartServiceParam +import io.getstream.video.android.core.notifications.internal.service.StopServiceParam +//import io.getstream.video.android.core.notifications.internal.service.TelecomHelper +//import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction +//import io.getstream.video.android.core.notifications.internal.telecom.JetpackTelecomRepository +//import io.getstream.video.android.core.telecom.TelecomPermissions +import io.getstream.video.android.core.utils.safeCallWithResult +import io.getstream.video.android.model.StreamCallId +import kotlinx.coroutines.launch + +/** + * TODO Rahul change the name of the class its a decision maker class which will decide which + * which service to pick + */ +class ServiceLauncher(val context: Context) { + + private val logger by taggedLogger("ServiceTriggers") + private val serviceIntentBuilder = ServiceIntentBuilder() + private val incomingCallPresenter = IncomingCallPresenter(serviceIntentBuilder) +// private val telecomHelper = TelecomHelper() +// +// private val telecomServiceLauncher = TelecomServiceLauncher() + + @SuppressLint("MissingPermission") + fun showIncomingCall( + context: Context, + callId: StreamCallId, + callDisplayName: String?, + callServiceConfiguration: CallServiceConfig, + isVideo: Boolean, + payload: Map, + streamVideo: StreamVideo, + notification: Notification?, + ) { + val result = incomingCallPresenter.showIncomingCall( + context, + callId, + callDisplayName, + callServiceConfiguration, + notification, + ) +// val telecomPermissions = TelecomPermissions() +// if (telecomPermissions.canUseTelecom(context)) { +// // TODO Rahul, correctly use result to launch telecom +// when (result) { +// ShowIncomingCallResult.FG_SERVICE -> {} +// ShowIncomingCallResult.SERVICE -> {} +// ShowIncomingCallResult.ONLY_NOTIFICATION -> {} +// ShowIncomingCallResult.ERROR -> {} +// } +// +// updateIncomingCallNotification(notification, streamVideo, callId) +// +// if (telecomHelper.canUseJetpackTelecom()) { +// val jetpackTelecomRepository = getJetpackTelecomRepository(callId) +// +// val appSchema = (streamVideo as StreamVideoClient).telecomConfig?.schema +// val addressUri = "$appSchema:${callId.id}".toUri() +// val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT +// +// val call = streamVideo.call(callId.type, callId.id) +// +// call.state.jetpackTelecomRepository = jetpackTelecomRepository +// +// call.scope.launch { +// jetpackTelecomRepository.registerCall( +// formattedCallDisplayName, +// addressUri, +// true, +// ) +// } +// } else { +// telecomServiceLauncher.addIncomingCallToTelecom( +// context, +// callId, +// callDisplayName, +// callServiceConfiguration, +// isVideo, +// payload, +// streamVideo, +// notification, +// ) +// } +// } + } + + fun showOnGoingCall(call: Call, trigger: String, streamVideo: StreamVideo) { + val client = streamVideo as StreamVideoClient + val callConfig = client.callServiceConfigRegistry.get(call.type) + if (!callConfig.runCallServiceInForeground) { + return + } + val callId = StreamCallId.fromCallCid(call.cid) + val context = client.context + val serviceIntent = ServiceIntentBuilder().buildStartIntent( + context, + StartServiceParam( + callId, + trigger, + callServiceConfiguration = callConfig, + ), + ) + ContextCompat.startForegroundService(context, serviceIntent) + + val callDisplayName = "ON GOING CALL NOT SET" // TODO Rahul Later +// val telecomPermissions = TelecomPermissions() +// if (telecomPermissions.canUseTelecom(context)) { +// if (telecomHelper.canUseJetpackTelecom()) { + /** + * Do nothing, the logic already handled in [StreamCallActivity.accept()] + */ +// val jetpackTelecomRepository = getJetpackTelecomRepository(callId) +// +// val appSchema = streamVideo.telecomConfig?.schema +// val addressUri = "$appSchema:${callId.id}".toUri() +// val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT +// +// call.state.jetpackTelecomRepository = jetpackTelecomRepository +// +// call.scope.launch { +// jetpackTelecomRepository.registerCall( +// formattedCallDisplayName, +// addressUri, +// true, +// ) +// } +// } else { +// telecomServiceLauncher.addOnGoingCall( +// context, +// callId = StreamCallId(call.type, call.id), +// callDisplayName = callDisplayName, +// isVideo = call.isVideoEnabled(), +// streamVideo = streamVideo, +// ) +// } +// } + + } + + fun showOutgoingCall(call: Call, trigger: String, streamVideo: StreamVideo) { + val callConfig = (streamVideo as StreamVideoClient).callServiceConfigRegistry.get(call.type) + if (!callConfig.runCallServiceInForeground) { + return + } + val callId = StreamCallId.fromCallCid(call.cid) + val serviceIntent = ServiceIntentBuilder().buildStartIntent( + context, + StartServiceParam( + callId, + trigger, + callServiceConfiguration = callConfig, + ), + ) + + ContextCompat.startForegroundService(context, serviceIntent) + +// val callDisplayName = "NOT SET YET" // TODO Rahul +// +// val telecomPermissions = TelecomPermissions() +// val telecomHelper = TelecomHelper() +// if (telecomPermissions.canUseTelecom(context)) { +// if (telecomHelper.canUseJetpackTelecom()) { +// val jetpackTelecomRepository = getJetpackTelecomRepository(callId) +// +// val appSchema = streamVideo.telecomConfig?.schema +// val addressUri = "$appSchema:${callId.id}".toUri() +// val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT +// +// call.state.jetpackTelecomRepository = jetpackTelecomRepository +// +// call.scope.launch { +// jetpackTelecomRepository.registerCall( +// formattedCallDisplayName, +// addressUri, +// false, +// ) +// } +// } else { +// // TODO Rahul pending, use telecom platform api +// } +// } + } + + /** + * Because we need to retrieve the notification + * in [io.getstream.video.android.core.notifications.internal.telecom.connection.SuccessIncomingTelecomConnection] + */ + private fun updateIncomingCallNotification( + notification: Notification?, + streamVideo: StreamVideo, + callId: StreamCallId, + ) { + notification?.let { + streamVideo.call(callId.type, callId.id) + .state.updateNotification(notification) + } + } + + fun removeIncomingCall( + context: Context, + callId: StreamCallId, + config: CallServiceConfig = DefaultCallConfigurations.default, + ) { + safeCallWithResult { + context.startService( + serviceIntentBuilder.buildStartIntent( + context, + StartServiceParam( + callId, + TRIGGER_REMOVE_INCOMING_CALL, + callServiceConfiguration = config, + ), + ), + )!! + }.onError { + NotificationManagerCompat.from(context) + .cancel(callId.getNotificationId(NotificationType.Incoming)) + } + } + + fun stopService( + call: Call, + ) { +// logger.d { "stopService, call id: ${call.cid}, source: ${stopForegroundServiceSource.source}" } +// stopTelecomInternal(call, stopForegroundServiceSource) + stopCallServiceInternal(call) + } + +// @RequiresApi(Build.VERSION_CODES.O) +// private fun getJetpackTelecomRepository(callId: StreamCallId): JetpackTelecomRepository { +// val callsManager = CallsManager(context).apply { +// // Register with the telecom interface with the supported capabilities +// registerAppWithTelecom( +// capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING and +// CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING, +// ) +// } +// val streamVideo = StreamVideo.instance() +// val incomingCallPresenter = IncomingCallPresenter(ServiceIntentBuilder()) +// val incomingCallTelecomAction = +// IncomingCallTelecomAction(context, streamVideo, incomingCallPresenter) +// +// return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction) +// } +// +// private fun stopTelecomInternal( +// call: Call, +// stopForegroundServiceSource: StopForegroundServiceSource, +// ) { +// val telecomPermissions = TelecomPermissions() +// if (telecomPermissions.canUseTelecom(context)) { +// call.state.telecomConnection.value?.let { +// when (stopForegroundServiceSource) { +// StopForegroundServiceSource.CallAccept -> {} +// StopForegroundServiceSource.RemoveActiveCall -> { +// it.setDisconnected(DisconnectCause(DisconnectCause.CANCELED)) +// it.destroy() +// } +// +// StopForegroundServiceSource.RemoveRingingCall -> {} +// StopForegroundServiceSource.SetActiveCall -> {} +// } +// } +// } +// } + + private fun stopCallServiceInternal(call: Call) { + val streamVideo = StreamVideo.instanceOrNull() as? StreamVideoClient + streamVideo?.let { streamVideoClient -> + val callConfig = streamVideoClient.callServiceConfigRegistry.get(call.type) + if (callConfig.runCallServiceInForeground) { + val context = streamVideoClient.context + + val serviceIntent = serviceIntentBuilder.buildStopIntent( + context, + StopServiceParam(call, callConfig), + ) + logger.d { "Building stop intent for call_id: ${call.cid}" } + serviceIntent.extras?.let { + logBundle(it) + } + context.startService(serviceIntent) + } + } + } + + private fun logBundle(bundle: Bundle) { + val keys = bundle.keySet() + if (keys != null) { + val sb = StringBuilder() + for (key in keys) { + val itemInBundle = bundle[key] + val text = "key:$key, value=$itemInBundle" + sb.append(text) + sb.append("\n") + } + if (sb.toString().isNotEmpty()) { + logger.d { " [maybeStopForegroundService], stop intent extras: $sb" } + } + } + } +} + diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt new file mode 100644 index 00000000000..f45e949cdc8 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.service + +import android.app.Notification +import android.content.Context +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.R +import io.getstream.video.android.core.RingingState +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.NotificationConfig +import io.getstream.video.android.core.notifications.NotificationType +import io.getstream.video.android.core.notifications.handlers.StreamDefaultNotificationHandler +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_OUTGOING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL +import io.getstream.video.android.model.StreamCallId + +internal class ServiceNotificationRetriever { + private val logger by taggedLogger("ServiceNotificationRetriever") + + open fun getNotificationPair( + context: Context, + trigger: String, + streamVideo: StreamVideoClient, + streamCallId: StreamCallId, + intentCallDisplayName: String?, + ): Pair { + logger.d { + "[getNotificationPair] trigger: $trigger, callId: ${streamCallId.id}, callDisplayName: $intentCallDisplayName" + } + val notificationData: Pair = when (trigger) { + TRIGGER_ONGOING_CALL -> { + logger.d { "[getNotificationPair] Creating ongoing call notification" } + Pair( + first = streamVideo.getOngoingCallNotification( + callId = streamCallId, + callDisplayName = intentCallDisplayName, + payload = emptyMap(), + ), + second = streamCallId.hashCode(), + ) + } + + TRIGGER_INCOMING_CALL -> { + logger.d { "[getNotificationPair] Creating incoming call notification" } + val shouldHaveContentIntent = streamVideo.state.activeCall.value == null + logger.d { "[getNotificationPair] shouldHaveContentIntent: $shouldHaveContentIntent" } + Pair( + first = streamVideo.getRingingCallNotification( + ringingState = RingingState.Incoming(), + callId = streamCallId, + callDisplayName = intentCallDisplayName, + shouldHaveContentIntent = shouldHaveContentIntent, + payload = emptyMap(), + ), + second = streamCallId.getNotificationId(NotificationType.Incoming), + ) + } + + TRIGGER_OUTGOING_CALL -> { + logger.d { "[getNotificationPair] Creating outgoing call notification" } + Pair( + first = streamVideo.getRingingCallNotification( + ringingState = RingingState.Outgoing(), + callId = streamCallId, + callDisplayName = context.getString( + R.string.stream_video_outgoing_call_notification_title, + ), + payload = emptyMap(), + ), + second = streamCallId.getNotificationId( + NotificationType.Incoming, // TODO Rahul, should we change it to outgoing? + ), // Same for incoming and outgoing + ) + } + + TRIGGER_REMOVE_INCOMING_CALL -> { + logger.d { "[getNotificationPair] Removing incoming call notification" } + Pair(null, streamCallId.getNotificationId(NotificationType.Incoming)) + } + + else -> { + logger.w { "[getNotificationPair] Unknown trigger: $trigger" } + Pair(null, streamCallId.hashCode()) + } + } + logger.d { + "[getNotificationPair] Generated notification: ${notificationData.first != null}, notificationId: ${notificationData.second}" + } + return notificationData + } + + fun notificationConfig(): NotificationConfig { + val client = StreamVideo.instanceOrNull() as StreamVideoClient + return client.streamNotificationManager.notificationConfig + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt new file mode 100644 index 00000000000..4b99325dd8f --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt @@ -0,0 +1,23 @@ +package io.getstream.video.android.core.notifications.internal.service + +import io.getstream.video.android.core.Call +import io.getstream.video.android.model.StreamCallId + +data class StartServiceParam( + val callId: StreamCallId, + val trigger: String, + val callDisplayName: String? = null, + val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, +) + +data class StopServiceParam( + val call: Call? = null, + val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, +) + +sealed class StopForegroundServiceSource(val source: String) { + data object CallAccept : StopForegroundServiceSource("accept the call") + data object SetActiveCall : StopForegroundServiceSource("set active call") + data object RemoveActiveCall : StopForegroundServiceSource("remove active call") + data object RemoveRingingCall : StopForegroundServiceSource("remove ringing call") +} From c4335a09b7e5c30ff73c9c0d891a6bfae8059b81 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 6 Oct 2025 17:13:09 +0530 Subject: [PATCH 02/29] phase 2: integrate jetpack telecom --- .../android/util/StreamVideoInitHelper.kt | 3 + .../api/stream-video-android-core.api | 436 +++++++++++++++++- stream-video-android-core/build.gradle.kts | 3 + .../src/main/AndroidManifest.xml | 2 + .../io/getstream/video/android/core/Call.kt | 4 +- .../getstream/video/android/core/CallState.kt | 11 + .../video/android/core/ClientState.kt | 4 - .../core/ExternalCallRejectionHandler.kt | 79 ++++ .../video/android/core/StreamVideoBuilder.kt | 19 + .../video/android/core/StreamVideoClient.kt | 13 +- .../notifications/IncomingNotificationData.kt | 26 ++ .../StreamDefaultNotificationHandler.kt | 16 +- .../receivers/RejectCallBroadcastReceiver.kt | 34 +- .../internal/service/CallService.kt | 23 +- .../internal/service/IncomingCallPresenter.kt | 7 +- .../internal/service/ServiceIntentBuilder.kt | 4 +- .../internal/service/ServiceLauncher.kt | 252 +++++----- .../service/ServiceNotificationRetriever.kt | 1 - .../internal/service/StartServiceParam.kt | 16 + .../telecom/IncomingCallTelecomAction.kt | 85 ++++ .../telecom/JetpackTelecomRepository.kt | 328 +++++++++++++ .../telecom/OutgoingCallTelecomAction.kt | 27 ++ .../internal/telecom/TelecomCall.kt | 74 +++ .../internal/telecom/TelecomCallAction.kt | 59 +++ .../internal/telecom/TelecomCallController.kt | 97 ++++ .../internal/telecom/TelecomConfig.kt | 19 + .../internal/telecom/TelecomHelper.kt | 26 ++ .../internal/telecom/TelecomPermissions.kt | 114 +++++ .../internal/telecom/TelecomServiceParam.kt | 34 ++ .../telecom/ui/ActivityLifecycleCallbacks.kt | 64 +++ .../telecom/ui/TelecomPermissionHandler.kt | 79 ++++ .../ui/TelecomPermissionRequestActivity.kt | 70 +++ .../android/core/sounds/CallSoundPlayer.kt | 4 +- .../android/ui/common/StreamCallActivity.kt | 22 +- 34 files changed, 1861 insertions(+), 194 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/IncomingNotificationData.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 79bbc697fb7..04282395921 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -39,6 +39,7 @@ import io.getstream.video.android.core.notifications.NotificationConfig import io.getstream.video.android.core.notifications.handlers.CompatibilityStreamNotificationHandler import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations +import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.data.services.stream.GetAuthDataResponse import io.getstream.video.android.data.services.stream.StreamService @@ -213,6 +214,7 @@ object StreamVideoInitHelper { apiKey = apiKey, user = user, token = token, + connectionTimeoutInMs = 12_000L, loggingLevel = loggingLevel, ensureSingleInstance = false, callServiceConfigRegistry = callServiceConfigRegistry, @@ -290,6 +292,7 @@ object StreamVideoInitHelper { callUpdatesAfterLeave = true, appName = "Stream Video Demo App", audioProcessing = NoiseCancellation(context), + telecomConfig = TelecomConfig(context.packageName, true), ).build() } } diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index ef2fc0f57b0..faceb94fec2 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -5513,8 +5513,8 @@ public final class io/getstream/video/android/core/Call { public final fun processAudioSample (Lorg/webrtc/audio/JavaAudioDeviceModule$AudioSamples;)V public final fun queryMembers (Ljava/util/Map;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun queryMembers$default (Lio/getstream/video/android/core/Call;Ljava/util/Map;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun reject (Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun reject$default (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun reject (Ljava/lang/String;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun reject$default (Lio/getstream/video/android/core/Call;Ljava/lang/String;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun rejoin (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun removeMembers (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun requestPermissions ([Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -5759,6 +5759,7 @@ public final class io/getstream/video/android/core/ClientState { public final fun getCallConfigRegistry ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry; public final fun getConnection ()Lkotlinx/coroutines/flow/StateFlow; public final fun getRingingCall ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getServiceLauncher ()Lio/getstream/video/android/core/notifications/internal/service/ServiceLauncher; public final fun getUser ()Lkotlinx/coroutines/flow/StateFlow; public final fun handleError (Lio/getstream/result/Error;)V public final fun handleEvent (Lio/getstream/android/video/generated/models/VideoEvent;)V @@ -5870,6 +5871,14 @@ public final class io/getstream/video/android/core/EventSubscription { public final fun setDisposed (Z)V } +public final class io/getstream/video/android/core/ExternalCallRejectionSource : java/lang/Enum { + public static final field NOTIFICATION Lio/getstream/video/android/core/ExternalCallRejectionSource; + public static final field WEARABLE Lio/getstream/video/android/core/ExternalCallRejectionSource; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/ExternalCallRejectionSource; + public static fun values ()[Lio/getstream/video/android/core/ExternalCallRejectionSource; +} + public abstract class io/getstream/video/android/core/GEO { } @@ -6337,7 +6346,8 @@ public final class io/getstream/video/android/core/StreamVideoBuilder { public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZ)V public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZ)V public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZ)V - public synthetic fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;)V + public synthetic fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lio/getstream/video/android/core/StreamVideo; } @@ -10422,6 +10432,426 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final fun getLivestreamGuestCallServiceConfig ()Ljava/util/Map; } +public final class io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter { + public fun (Lio/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder;)V + public final fun showIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Landroid/app/Notification;)Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; +} + +public final class io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder { + public fun ()V + public final fun buildStartIntent (Landroid/content/Context;Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam;)Landroid/content/Intent; + public final fun buildStopIntent (Landroid/content/Context;Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam;)Landroid/content/Intent; +} + +public final class io/getstream/video/android/core/notifications/internal/service/ServiceLauncher { + public fun (Landroid/content/Context;)V + public final fun getContext ()Landroid/content/Context; + public final fun getTelecomPermissions ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions; + public final fun removeIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V + public static synthetic fun removeIncomingCall$default (Lio/getstream/video/android/core/notifications/internal/service/ServiceLauncher;Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)V + public final fun showIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZLjava/util/Map;Lio/getstream/video/android/core/StreamVideo;Landroid/app/Notification;)V + public final fun showOnGoingCall (Lio/getstream/video/android/core/Call;Ljava/lang/String;Lio/getstream/video/android/core/StreamVideo;)V + public final fun showOutgoingCall (Lio/getstream/video/android/core/Call;Ljava/lang/String;Lio/getstream/video/android/core/StreamVideo;)V + public final fun stopService (Lio/getstream/video/android/core/Call;)V +} + +public final class io/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult : java/lang/Enum { + public static final field ERROR Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; + public static final field FG_SERVICE Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; + public static final field ONLY_NOTIFICATION Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; + public static final field SERVICE Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; + public static fun values ()[Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; +} + +public final class io/getstream/video/android/core/notifications/internal/service/StartServiceParam { + public fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V + public synthetic fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/model/StreamCallId; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun copy (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/service/StartServiceParam; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/StartServiceParam;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/StartServiceParam; + public fun equals (Ljava/lang/Object;)Z + public final fun getCallDisplayName ()Ljava/lang/String; + public final fun getCallId ()Lio/getstream/video/android/model/StreamCallId; + public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun getTrigger ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getSource ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$CallAccept : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { + public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$CallAccept; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveActiveCall : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { + public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveActiveCall; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveRingingCall : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { + public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveRingingCall; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$SetActiveCall : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { + public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$SetActiveCall; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/service/StopServiceParam { + public fun ()V + public fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V + public synthetic fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/Call; + public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun copy (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/service/StopServiceParam; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/StopServiceParam;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/StopServiceParam; + public fun equals (Ljava/lang/Object;)Z + public final fun getCall ()Lio/getstream/video/android/core/Call; + public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/DisconnectSource : java/lang/Enum { + public static final field PHONE Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; + public static final field WEARABLE Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; + public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction { + public fun (Landroid/content/Context;Lio/getstream/video/android/core/StreamVideo;Lio/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter;)V + public final fun onAnswer (Lio/getstream/video/android/model/StreamCallId;)V + public final fun onDisconnect (Lio/getstream/video/android/model/StreamCallId;)V + public final fun onShowIncomingCallUi (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Landroid/app/Notification;)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository { + public fun (Landroidx/core/telecom/CallsManager;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction;)V + public final fun getCallId ()Lio/getstream/video/android/model/StreamCallId; + public final fun getCurrentCall ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getOnIsCallActive ()Lkotlin/jvm/functions/Function1; + public final fun getOnIsCallAnswered ()Lkotlin/jvm/functions/Function2; + public final fun getOnIsCallDisconnected ()Lkotlin/jvm/functions/Function2; + public final fun getOnIsCallInactive ()Lkotlin/jvm/functions/Function1; + public final fun registerCall (Ljava/lang/String;Landroid/net/Uri;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction { + public fun (Lio/getstream/video/android/core/StreamVideo;)V + public final fun getStreamVideo ()Lio/getstream/video/android/core/StreamVideo; + public final fun onDisconnect (Lio/getstream/video/android/model/StreamCallId;)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/StartServiceParam { + public fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V + public synthetic fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/model/StreamCallId; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun copy (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam; + public fun equals (Ljava/lang/Object;)Z + public final fun getCallDisplayName ()Ljava/lang/String; + public final fun getCallId ()Lio/getstream/video/android/model/StreamCallId; + public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun getTrigger ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/StopServiceParam { + public fun ()V + public fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V + public synthetic fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/Call; + public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun copy (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam; + public fun equals (Ljava/lang/Object;)Z + public final fun getCall ()Lio/getstream/video/android/core/Call; + public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall$None : io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { + public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$None; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered : io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { + public fun (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;ZZZLjava/lang/Integer;Landroidx/core/telecom/CallEndpointCompat;Ljava/util/List;Lkotlinx/coroutines/channels/Channel;)V + public final fun component1 ()Landroid/os/ParcelUuid; + public final fun component2 ()Landroidx/core/telecom/CallAttributesCompat; + public final fun component3 ()Z + public final fun component4 ()Z + public final fun component5 ()Z + public final fun component6 ()Ljava/lang/Integer; + public final fun component7 ()Landroidx/core/telecom/CallEndpointCompat; + public final fun component8 ()Ljava/util/List; + public final fun copy (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;ZZZLjava/lang/Integer;Landroidx/core/telecom/CallEndpointCompat;Ljava/util/List;Lkotlinx/coroutines/channels/Channel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered;Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;ZZZLjava/lang/Integer;Landroidx/core/telecom/CallEndpointCompat;Ljava/util/List;Lkotlinx/coroutines/channels/Channel;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered; + public fun equals (Ljava/lang/Object;)Z + public final fun getAvailableCallEndpoints ()Ljava/util/List; + public final fun getCallAttributes ()Landroidx/core/telecom/CallAttributesCompat; + public final fun getCurrentCallEndpoint ()Landroidx/core/telecom/CallEndpointCompat; + public final fun getErrorCode ()Ljava/lang/Integer; + public final fun getId ()Landroid/os/ParcelUuid; + public fun hashCode ()I + public final fun isActive ()Z + public final fun isIncoming ()Z + public final fun isMuted ()Z + public final fun isOnHold ()Z + public final fun processAction (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction;)Z + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered : io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { + public fun (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;Landroid/telecom/DisconnectCause;)V + public final fun component1 ()Landroid/os/ParcelUuid; + public final fun component2 ()Landroidx/core/telecom/CallAttributesCompat; + public final fun component3 ()Landroid/telecom/DisconnectCause; + public final fun copy (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;Landroid/telecom/DisconnectCause;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered;Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;Landroid/telecom/DisconnectCause;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered; + public fun equals (Ljava/lang/Object;)Z + public final fun getCallAttributes ()Landroidx/core/telecom/CallAttributesCompat; + public final fun getDisconnectCause ()Landroid/telecom/DisconnectCause; + public final fun getId ()Landroid/os/ParcelUuid; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction : android/os/Parcelable { +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; + public fun describeContents ()I + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun (Z)V + public final fun component1 ()Z + public final fun copy (Z)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun isAudioCall ()Z + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource;)V + public final fun component1 ()Landroid/telecom/DisconnectCause; + public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; + public final fun copy (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect;Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getCause ()Landroid/telecom/DisconnectCause; + public final fun getSource ()Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold; + public fun describeContents ()I + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun (Landroid/os/ParcelUuid;)V + public final fun component1 ()Landroid/os/ParcelUuid; + public final fun copy (Landroid/os/ParcelUuid;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint;Landroid/os/ParcelUuid;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getEndpointId ()Landroid/os/ParcelUuid; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun (Z)V + public final fun component1 ()Z + public final fun copy (Z)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun isMute ()Z + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun (Landroid/os/ParcelUuid;)V + public final fun component1 ()Landroid/os/ParcelUuid; + public final fun copy (Landroid/os/ParcelUuid;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall;Landroid/os/ParcelUuid;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getEndpointId ()Landroid/os/ParcelUuid; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController { + public fun (Landroid/content/Context;)V + public final fun getContext ()Landroid/content/Context; + public final fun leaveCurrentCall (Lio/getstream/video/android/core/Call;)V + public final fun onAnswer (Lio/getstream/video/android/core/Call;)V + public final fun onCancelOutgoingCall (Lio/getstream/video/android/core/Call;)V + public final fun onDeclineOngoingCall (Lio/getstream/video/android/core/Call;)V + public final fun onRejectFromNotification (Lio/getstream/video/android/core/Call;)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig { + public fun (Ljava/lang/String;Z)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Z + public final fun copy (Ljava/lang/String;Z)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Ljava/lang/String;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getRequestPermissionOnAppLaunch ()Z + public final fun getSchema ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper { + public fun ()V + public final fun canUseJetpackTelecom ()Z +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions { + public fun ()V + public final fun canUseTelecom (Landroid/content/Context;)Z + public final fun getRequiredPermissionsArray ()[Ljava/lang/String; + public final fun getRequiredPermissionsList ()Ljava/util/List; + public final fun hasPermissions (Landroid/content/Context;)Z + public final fun requestPermissions (Landroidx/activity/ComponentActivity;Lkotlin/jvm/functions/Function1;)V + public final fun supportsTelecom (Landroid/content/Context;)Z +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks : android/app/Application$ActivityLifecycleCallbacks { + public fun ()V + public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V + public fun onActivityDestroyed (Landroid/app/Activity;)V + public fun onActivityPaused (Landroid/app/Activity;)V + public fun onActivityResumed (Landroid/app/Activity;)V + public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V + public fun onActivityStarted (Landroid/app/Activity;)V + public fun onActivityStopped (Landroid/app/Activity;)V + public fun onFirstActivityStarted (Landroid/app/Activity;)V + public fun onLastActivityStopped (Landroid/app/Activity;)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler : io/getstream/android/push/permissions/ActivityLifecycleCallbacks { + public static final field Companion Lio/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler$Companion; + public fun onActivityStarted (Landroid/app/Activity;)V + public fun onFirstActivityStarted (Landroid/app/Activity;)V + public fun onLastActivityStopped (Landroid/app/Activity;)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler$Companion { + public final fun instance (Landroid/app/Application;)Lio/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler; +} + public final class io/getstream/video/android/core/notifications/medianotifications/MediaNotificationConfig { public fun (Lio/getstream/video/android/core/notifications/medianotifications/MediaNotificationContent;Lio/getstream/video/android/core/notifications/medianotifications/MediaNotificationVisuals;Landroid/app/PendingIntent;)V public final fun component1 ()Lio/getstream/video/android/core/notifications/medianotifications/MediaNotificationContent; diff --git a/stream-video-android-core/build.gradle.kts b/stream-video-android-core/build.gradle.kts index 6e812528d63..1f35b7034d6 100644 --- a/stream-video-android-core/build.gradle.kts +++ b/stream-video-android-core/build.gradle.kts @@ -185,6 +185,9 @@ dependencies { implementation(libs.stream.push.delegate) api(libs.stream.push.permissions) + //permission for telecom + implementation(libs.androidx.activity) + //jetpack telecom implementation(libs.androidx.telecom) diff --git a/stream-video-android-core/src/main/AndroidManifest.xml b/stream-video-android-core/src/main/AndroidManifest.xml index a34cf775956..7138ae837a5 100644 --- a/stream-video-android-core/src/main/AndroidManifest.xml +++ b/stream-video-android-core/src/main/AndroidManifest.xml @@ -115,5 +115,7 @@ android:name=".notifications.internal.service.AudioCallService" android:foregroundServiceType="microphone|shortService" android:exported="false" /> + + \ No newline at end of file diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 30a1ede0a48..2c66b4c33f7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -1345,8 +1345,8 @@ public class Call( return clientImpl.accept(type, id) } - suspend fun reject(reason: RejectReason? = null): Result { - logger.d { "[reject] #ringing; rejectReason: $reason, call_id:$id" } + suspend fun reject(source: String = "n/a", reason: RejectReason? = null): Result { + logger.d { "[reject] source: $source, #ringing; rejectReason: $reason, call_id:$id" } return clientImpl.reject(type, id, reason) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index 05a3e7e9224..58b2b6410a9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -106,6 +106,8 @@ import io.getstream.video.android.core.model.Reaction import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.model.ScreenSharingSession import io.getstream.video.android.core.model.VisibilityOnScreenState +import io.getstream.video.android.core.notifications.IncomingNotificationData +import io.getstream.video.android.core.notifications.internal.telecom.JetpackTelecomRepository import io.getstream.video.android.core.permission.PermissionRequest import io.getstream.video.android.core.pinning.PinType import io.getstream.video.android.core.pinning.PinUpdateAtTime @@ -686,9 +688,18 @@ public class CallState( private val pendingParticipantsJoined = ConcurrentHashMap() + /** + * We re-create notification more than 1 times, so we don't want to + * overwrite to the notifications builder properties once it is already set + */ internal val atomicNotification: AtomicReference = AtomicReference(null) + @InternalStreamVideoApi + var jetpackTelecomRepository: JetpackTelecomRepository? = null + + internal var incomingNotificationData = IncomingNotificationData(emptyMap()) + fun handleEvent(event: VideoEvent) { logger.d { "[handleEvent] ${event::class.java.name.split(".").last()}" } when (event) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 4ab7c794ca9..822b019c3f4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -16,9 +16,7 @@ package io.getstream.video.android.core -import android.content.Intent import androidx.compose.runtime.Stable -import androidx.core.content.ContextCompat import io.getstream.android.video.generated.models.CallCreatedEvent import io.getstream.android.video.generated.models.CallRingEvent import io.getstream.android.video.generated.models.ConnectedEvent @@ -27,10 +25,8 @@ import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.notifications.internal.service.CallService import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher -import io.getstream.video.android.core.notifications.internal.service.StopForegroundServiceSource import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.utils.safeCallWithDefault -import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.User import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt new file mode 100644 index 00000000000..15b822ffcb4 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import io.getstream.log.taggedLogger +import io.getstream.result.Result +import io.getstream.video.android.core.model.RejectReason +import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher +import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallController +import io.getstream.video.android.model.StreamCallId + +internal class ExternalCallRejectionHandler() { + private val logger by taggedLogger("CallRejectionHandler") + + suspend fun onRejectCall(source: ExternalCallRejectionSource, call: Call, context: Context, intent: Intent = Intent()) { + when ( + val rejectResult = call.reject( + source = "ExternalCallRejectionHandler.$source", + RejectReason.Decline, + ) + ) { + is Result.Success -> { + val userId = StreamVideo.instanceOrNull()?.userId + userId?.let { + val set = mutableSetOf(it) + call.state.updateRejectedBy(set) + call.state.updateRejectActionBundle(intent.extras ?: Bundle()) + } + logger.d { "[onRejectCall] source:$source rejectCall, Success: $rejectResult" } + } + is Result.Failure -> { + logger.d { "[onRejectCall] source:$source, rejectCall, Failure: $rejectResult" } + } + } + logger.d { "[onRejectCall] source:$source, #ringing; callId: ${call.id}, action: ${intent.action}" } + + val serviceLauncher = ServiceLauncher(context) + serviceLauncher.removeIncomingCall( + context, + StreamCallId.fromCallCid(call.cid), + StreamVideo.instance().state.callConfigRegistry.get(call.type), + ) + when (source) { + ExternalCallRejectionSource.NOTIFICATION -> { + TelecomCallController(context) + .onRejectFromNotification(call) + } + ExternalCallRejectionSource.WEARABLE -> { + /** + * Following the same logic from StreamCallActivity.reject(Call, RejectReason?, + * onSuccess: (suspend (Call) -> Unit)?, + * onError: (suspend (Exception) -> Unit)?,) + */ + call.leave() + } + } + } +} + +enum class ExternalCallRejectionSource { + NOTIFICATION, WEARABLE +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index f8f87099ad3..caa4ee83600 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -16,6 +16,7 @@ package io.getstream.video.android.core +import android.app.Application import android.app.Notification import android.content.Context import androidx.lifecycle.ProcessLifecycleOwner @@ -32,6 +33,8 @@ import io.getstream.video.android.core.notifications.internal.StreamNotification import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.storage.DeviceTokenStorage +import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig +import io.getstream.video.android.core.notifications.internal.telecom.ui.TelecomPermissionHandler import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.common.scope.ClientScope @@ -139,6 +142,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( private val enableStatsReporting: Boolean = true, @InternalStreamVideoApi private val enableStereoForSubscriber: Boolean = true, + private val telecomConfig: TelecomConfig? = null, ) { private val context: Context = context.applicationContext private val scope = UserScope(ClientScope()) @@ -265,6 +269,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( enableCallUpdatesAfterLeave = callUpdatesAfterLeave, enableStatsCollection = enableStatsReporting, enableStereoForSubscriber = enableStereoForSubscriber, + telecomConfig = telecomConfig, ) if (user.type == UserType.Guest) { @@ -300,6 +305,20 @@ public class StreamVideoBuilder @JvmOverloads constructor( streamLog { "location initialized: ${location.getOrNull()}" } } + /** + * TODO Rahul Later: Ask telecom permission (maybe not needed at startup because we should have registered it in the Content provider) + */ + telecomConfig?.let { + if (it.requestPermissionOnAppLaunch) { + val app = (context.applicationContext as Application) + with(app) { + registerActivityLifecycleCallbacks( + TelecomPermissionHandler.instance(app), + ) + } + } + } + // Installs Stream Video instance StreamVideo.install(client) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index dd1f6e23fa3..df2d47bee03 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -16,6 +16,7 @@ package io.getstream.video.android.core +import android.app.Application import android.content.Context import android.media.AudioAttributes import androidx.collection.LruCache @@ -96,10 +97,11 @@ import io.getstream.video.android.core.model.toRequest import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.core.notifications.internal.StreamNotificationManager import io.getstream.video.android.core.notifications.internal.service.ANY_MARKER -import io.getstream.video.android.core.notifications.internal.service.CallService import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder -import io.getstream.video.android.core.notifications.internal.service.StopServiceParam +import io.getstream.video.android.core.notifications.internal.telecom.StopServiceParam +import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig +import io.getstream.video.android.core.notifications.internal.telecom.ui.TelecomPermissionHandler import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.ErrorResponse @@ -172,6 +174,7 @@ internal class StreamVideoClient internal constructor( internal val enableCallUpdatesAfterLeave: Boolean = false, internal val enableStatsCollection: Boolean = true, internal val enableStereoForSubscriber: Boolean = true, + internal val telecomConfig: TelecomConfig? = null, ) : StreamVideo, NotificationHandler by streamNotificationManager { private var locationJob: Deferred>? = null @@ -221,7 +224,6 @@ internal class StreamVideoClient internal constructor( val callConfig = callServiceConfigRegistry.get(activeCall?.type ?: ANY_MARKER) val runCallServiceInForeground = callConfig.runCallServiceInForeground if (runCallServiceInForeground) { - safeCall { val serviceIntent = ServiceIntentBuilder().buildStopIntent( context = context, @@ -233,6 +235,11 @@ internal class StreamVideoClient internal constructor( } } activeCall?.leave() + + val app = (context.applicationContext as Application) + with(app) { + unregisterActivityLifecycleCallbacks(TelecomPermissionHandler.instance(app)) + } } /** diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/IncomingNotificationData.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/IncomingNotificationData.kt new file mode 100644 index 00000000000..08b1494ea08 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/IncomingNotificationData.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications + +import android.app.PendingIntent + +internal data class IncomingNotificationData(val pendingIntentMap: Map) + +internal sealed class IncomingNotificationAction { + data object Accept : IncomingNotificationAction() + data object Reject : IncomingNotificationAction() +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt index 018074957e2..84ebc3e4a12 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt @@ -49,6 +49,8 @@ import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver import io.getstream.video.android.core.notifications.DefaultStreamIntentResolver +import io.getstream.video.android.core.notifications.IncomingNotificationAction +import io.getstream.video.android.core.notifications.IncomingNotificationData import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_LIVE_CALL import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_MISSED_CALL import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_NOTIFICATION @@ -56,7 +58,6 @@ import io.getstream.video.android.core.notifications.StreamIntentResolver import io.getstream.video.android.core.notifications.dispatchers.DefaultNotificationDispatcher import io.getstream.video.android.core.notifications.dispatchers.NotificationDispatcher import io.getstream.video.android.core.notifications.extractor.DefaultNotificationContentExtractor -import io.getstream.video.android.core.notifications.internal.service.CallService import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher import io.getstream.video.android.core.utils.isAppInForeground import io.getstream.video.android.core.utils.safeCall @@ -313,6 +314,19 @@ constructor( payload = payload, ) + val streamVideo = StreamVideo.instanceOrNull() + streamVideo?.let { streamVideoInstance -> + val call = streamVideoInstance.call(callId.type, callId.id) + val map = HashMap() + acceptCallPendingIntent?.let { pendingIntent -> + map[IncomingNotificationAction.Accept] = pendingIntent + } + rejectCallPendingIntent?.let { pendingIntent -> + map[IncomingNotificationAction.Reject] = pendingIntent + } + call.state.incomingNotificationData = IncomingNotificationData(map) + } + if (fullScreenPendingIntent != null && acceptCallPendingIntent != null && rejectCallPendingIntent != null) { getIncomingCallNotification( fullScreenPendingIntent, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt index 912d554f70a..a4fecf8bb66 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiver.kt @@ -18,16 +18,11 @@ package io.getstream.video.android.core.notifications.internal.receivers import android.content.Context import android.content.Intent -import android.os.Bundle import io.getstream.log.taggedLogger -import io.getstream.result.Result import io.getstream.video.android.core.Call -import io.getstream.video.android.core.StreamVideo -import io.getstream.video.android.core.model.RejectReason +import io.getstream.video.android.core.ExternalCallRejectionHandler +import io.getstream.video.android.core.ExternalCallRejectionSource import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_REJECT_CALL -import io.getstream.video.android.core.notifications.internal.service.CallService -import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher -import io.getstream.video.android.model.StreamCallId /** * Used to process any pending intents that feature the [ACTION_REJECT_CALL] action. By consuming this @@ -38,29 +33,14 @@ internal class RejectCallBroadcastReceiver : GenericCallActionBroadcastReceiver( val logger by taggedLogger("Call:RejectReceiver") override val action = ACTION_REJECT_CALL + private val externalCallRejectionHandler = ExternalCallRejectionHandler() override suspend fun onReceive(call: Call, context: Context, intent: Intent) { - when (val rejectResult = call.reject(RejectReason.Decline)) { - is Result.Success -> { - val userId = StreamVideo.instanceOrNull()?.userId - userId?.let { - val set = mutableSetOf(it) - call.state.updateRejectedBy(set) - call.state.updateRejectActionBundle(intent.extras ?: Bundle()) - } - logger.d { "[onReceive] rejectCall, Success: $rejectResult" } - } - is Result.Failure -> { - logger.d { "[onReceive] rejectCall, Failure: $rejectResult" } - } - } - logger.d { "[onReceive] #ringing; callId: ${call.id}, action: ${intent.action}" } - - val serviceLauncher = ServiceLauncher(context) - serviceLauncher.removeIncomingCall( + externalCallRejectionHandler.onRejectCall( + ExternalCallRejectionSource.NOTIFICATION, + call, context, - StreamCallId.fromCallCid(call.cid), - StreamVideo.instance().state.callConfigRegistry.get(call.type), + intent, ) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index 5af8f3b4396..f0152ad4f5c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -39,7 +39,6 @@ import io.getstream.android.video.generated.models.LocalCallMissedEvent import io.getstream.log.StreamLog import io.getstream.log.taggedLogger import io.getstream.video.android.core.Call -import io.getstream.video.android.core.R import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.RingingState import io.getstream.video.android.core.StreamVideo @@ -116,16 +115,15 @@ internal open class CallService : Service() { * @param trigger one of [TRIGGER_INCOMING_CALL], [TRIGGER_OUTGOING_CALL] or [TRIGGER_ONGOING_CALL] * @param callDisplayName the display name. */ - @Deprecated("",level = DeprecationLevel.WARNING) + @Deprecated("", level = DeprecationLevel.WARNING) fun buildStartIntent( context: Context, callId: StreamCallId, trigger: String, callDisplayName: String? = null, callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, - ): Intent - { - if(true) throw RuntimeException("Kyun aagaya idhar?") + ): Intent { + if (true) throw RuntimeException("Kyun aagaya idhar?") val serviceClass = callServiceConfiguration.serviceClass StreamLog.i(TAG) { "Resolved service class: $serviceClass" } val serviceIntent = Intent(context, serviceClass) @@ -164,7 +162,7 @@ internal open class CallService : Service() { * * @param context the context. */ - @Deprecated("",level = DeprecationLevel.ERROR) + @Deprecated("", level = DeprecationLevel.ERROR) fun buildStopIntent( context: Context, call: Call? = null, @@ -185,7 +183,7 @@ internal open class CallService : Service() { intent.putExtra(EXTRA_STOP_SERVICE, true) } - @Deprecated("",level = DeprecationLevel.ERROR) + @Deprecated("", level = DeprecationLevel.ERROR) fun showIncomingCall( context: Context, callId: StreamCallId, @@ -253,7 +251,7 @@ internal open class CallService : Service() { } } - @Deprecated("",level = DeprecationLevel.ERROR) + @Deprecated("", level = DeprecationLevel.ERROR) fun removeIncomingCall( context: Context, callId: StreamCallId, @@ -675,7 +673,10 @@ internal open class CallService : Service() { is RingingState.RejectedByAll -> { ClientScope().launch { - call.reject(RejectReason.Decline) + call.reject( + source = "RingingState.RejectedByAll", + RejectReason.Decline, + ) // noob 2 } callSoundPlayer?.stopCallSound() stopService() @@ -890,7 +891,7 @@ internal open class CallService : Service() { if (ringingState is RingingState.Outgoing) { // If I'm calling, end the call for everyone serviceScope.launch { - call.reject(RejectReason.Custom("Android Service Task Removed")) + call.reject("noob", RejectReason.Custom("Android Service Task Removed")) logger.i { "[onTaskRemoved] Ended outgoing call for all users." } } } else if (ringingState is RingingState.Incoming) { @@ -900,7 +901,7 @@ internal open class CallService : Service() { if (memberCount == 2) { // ...and I'm the only one being called, end the call for both users serviceScope.launch { - call.reject() + call.reject(source = "memberCount == 2") logger.i { "[onTaskRemoved] Ended incoming call for both users." } } } else { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt index e27fbec49c0..54dd1ac3951 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt @@ -25,16 +25,13 @@ import androidx.core.content.ContextCompat import io.getstream.log.taggedLogger import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.notifications.NotificationType -import io.getstream.video.android.core.notifications.internal.service.CallService import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL -import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig -import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder -import io.getstream.video.android.core.notifications.internal.service.StartServiceParam +import io.getstream.video.android.core.notifications.internal.telecom.StartServiceParam import io.getstream.video.android.core.utils.safeCallWithResult import io.getstream.video.android.model.StreamCallId class IncomingCallPresenter(private val serviceIntentBuilder: ServiceIntentBuilder) { - private val logger by taggedLogger("ServiceTriggers") + private val logger by taggedLogger("IncomingCallPresenter") fun showIncomingCall( context: Context, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt index 937355e10c6..0663b27b1be 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt @@ -19,17 +19,17 @@ package io.getstream.video.android.core.notifications.internal.service import android.app.ActivityManager import android.content.Context import android.content.Intent -import io.getstream.log.StreamLog import io.getstream.log.taggedLogger import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_CID import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_DISPLAY_NAME -import io.getstream.video.android.core.notifications.internal.service.CallService.Companion import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.EXTRA_STOP_SERVICE import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_KEY import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_OUTGOING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL +import io.getstream.video.android.core.notifications.internal.telecom.StartServiceParam +import io.getstream.video.android.core.notifications.internal.telecom.StopServiceParam import io.getstream.video.android.core.utils.safeCallWithDefault import io.getstream.video.android.model.StreamCallId diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index 96a08df1cc1..0404c71049d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.getstream.video.android.core.notifications.internal.service /* @@ -21,7 +37,6 @@ import android.app.Notification import android.content.Context import android.os.Build import android.os.Bundle -import android.telecom.DisconnectCause import androidx.annotation.RequiresApi import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat @@ -34,17 +49,18 @@ import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.core.notifications.internal.VideoPushDelegate.Companion.DEFAULT_CALL_TEXT import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL -import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig -import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations -import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder -import io.getstream.video.android.core.notifications.internal.service.StartServiceParam -import io.getstream.video.android.core.notifications.internal.service.StopServiceParam -//import io.getstream.video.android.core.notifications.internal.service.TelecomHelper -//import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction -//import io.getstream.video.android.core.notifications.internal.telecom.JetpackTelecomRepository -//import io.getstream.video.android.core.telecom.TelecomPermissions +import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction +import io.getstream.video.android.core.notifications.internal.telecom.JetpackTelecomRepository +import io.getstream.video.android.core.notifications.internal.telecom.StartServiceParam +import io.getstream.video.android.core.notifications.internal.telecom.StopServiceParam +import io.getstream.video.android.core.notifications.internal.telecom.TelecomCall +import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallAction +import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper +import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions import io.getstream.video.android.core.utils.safeCallWithResult import io.getstream.video.android.model.StreamCallId +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch /** @@ -56,9 +72,8 @@ class ServiceLauncher(val context: Context) { private val logger by taggedLogger("ServiceTriggers") private val serviceIntentBuilder = ServiceIntentBuilder() private val incomingCallPresenter = IncomingCallPresenter(serviceIntentBuilder) -// private val telecomHelper = TelecomHelper() -// -// private val telecomServiceLauncher = TelecomServiceLauncher() + private val telecomHelper = TelecomHelper() + val telecomPermissions = TelecomPermissions() @SuppressLint("MissingPermission") fun showIncomingCall( @@ -78,49 +93,38 @@ class ServiceLauncher(val context: Context) { callServiceConfiguration, notification, ) -// val telecomPermissions = TelecomPermissions() -// if (telecomPermissions.canUseTelecom(context)) { -// // TODO Rahul, correctly use result to launch telecom -// when (result) { -// ShowIncomingCallResult.FG_SERVICE -> {} -// ShowIncomingCallResult.SERVICE -> {} -// ShowIncomingCallResult.ONLY_NOTIFICATION -> {} -// ShowIncomingCallResult.ERROR -> {} -// } -// -// updateIncomingCallNotification(notification, streamVideo, callId) -// -// if (telecomHelper.canUseJetpackTelecom()) { -// val jetpackTelecomRepository = getJetpackTelecomRepository(callId) -// -// val appSchema = (streamVideo as StreamVideoClient).telecomConfig?.schema -// val addressUri = "$appSchema:${callId.id}".toUri() -// val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT -// -// val call = streamVideo.call(callId.type, callId.id) -// -// call.state.jetpackTelecomRepository = jetpackTelecomRepository -// -// call.scope.launch { -// jetpackTelecomRepository.registerCall( -// formattedCallDisplayName, -// addressUri, -// true, -// ) -// } -// } else { -// telecomServiceLauncher.addIncomingCallToTelecom( -// context, -// callId, -// callDisplayName, -// callServiceConfiguration, -// isVideo, -// payload, -// streamVideo, -// notification, -// ) -// } -// } + logger.d { "[showIncomingCall] service start result: $result" } + if (telecomPermissions.canUseTelecom(context)) { + if (telecomHelper.canUseJetpackTelecom()) { + // TODO Rahul, correctly use result to launch telecom + when (result) { + ShowIncomingCallResult.FG_SERVICE -> {} + ShowIncomingCallResult.SERVICE -> {} + ShowIncomingCallResult.ONLY_NOTIFICATION -> {} + ShowIncomingCallResult.ERROR -> {} + } + + updateIncomingCallNotification(notification, streamVideo, callId) + + val jetpackTelecomRepository = getJetpackTelecomRepository(callId) + + val appSchema = (streamVideo as StreamVideoClient).telecomConfig?.schema + val addressUri = "$appSchema:${callId.id}".toUri() + val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT + + val call = streamVideo.call(callId.type, callId.id) + + call.state.jetpackTelecomRepository = jetpackTelecomRepository + + call.scope.launch { + jetpackTelecomRepository.registerCall( + formattedCallDisplayName, + addressUri, + true, + ) + } + } + } } fun showOnGoingCall(call: Call, trigger: String, streamVideo: StreamVideo) { @@ -141,13 +145,13 @@ class ServiceLauncher(val context: Context) { ) ContextCompat.startForegroundService(context, serviceIntent) - val callDisplayName = "ON GOING CALL NOT SET" // TODO Rahul Later +// val callDisplayName = "ON GOING CALL NOT SET" // TODO Rahul Later // val telecomPermissions = TelecomPermissions() // if (telecomPermissions.canUseTelecom(context)) { // if (telecomHelper.canUseJetpackTelecom()) { - /** - * Do nothing, the logic already handled in [StreamCallActivity.accept()] - */ +// /** +// * Do nothing, the logic already handled in [StreamCallActivity.accept()] +// */ // val jetpackTelecomRepository = getJetpackTelecomRepository(callId) // // val appSchema = streamVideo.telecomConfig?.schema @@ -163,7 +167,8 @@ class ServiceLauncher(val context: Context) { // true, // ) // } -// } else { +// } + // else { // telecomServiceLauncher.addOnGoingCall( // context, // callId = StreamCallId(call.type, call.id), @@ -173,7 +178,6 @@ class ServiceLauncher(val context: Context) { // ) // } // } - } fun showOutgoingCall(call: Call, trigger: String, streamVideo: StreamVideo) { @@ -193,31 +197,39 @@ class ServiceLauncher(val context: Context) { ContextCompat.startForegroundService(context, serviceIntent) -// val callDisplayName = "NOT SET YET" // TODO Rahul -// -// val telecomPermissions = TelecomPermissions() -// val telecomHelper = TelecomHelper() -// if (telecomPermissions.canUseTelecom(context)) { -// if (telecomHelper.canUseJetpackTelecom()) { -// val jetpackTelecomRepository = getJetpackTelecomRepository(callId) -// -// val appSchema = streamVideo.telecomConfig?.schema -// val addressUri = "$appSchema:${callId.id}".toUri() -// val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT -// -// call.state.jetpackTelecomRepository = jetpackTelecomRepository -// -// call.scope.launch { -// jetpackTelecomRepository.registerCall( -// formattedCallDisplayName, -// addressUri, -// false, -// ) -// } -// } else { -// // TODO Rahul pending, use telecom platform api -// } -// } + val callDisplayName = "NOT SET YET" // TODO Rahul + + val telecomPermissions = TelecomPermissions() + val telecomHelper = TelecomHelper() + if (telecomPermissions.canUseTelecom(context)) { + if (telecomHelper.canUseJetpackTelecom()) { + val jetpackTelecomRepository = getJetpackTelecomRepository(callId) + + val appSchema = streamVideo.telecomConfig?.schema + val addressUri = "$appSchema:${callId.id}".toUri() + val formattedCallDisplayName = + callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT + + call.state.jetpackTelecomRepository = jetpackTelecomRepository + + call.scope.launch(Dispatchers.Default) { + launch { + jetpackTelecomRepository.registerCall( + formattedCallDisplayName, + addressUri, + false, + ) + } + launch { + delay(2000L) + val result = (jetpackTelecomRepository.currentCall.value as? TelecomCall.Registered)?.processAction( + TelecomCallAction.Activate, + ) + logger.d { "Telecom is activated: $result" } + } + } + } + } } /** @@ -257,51 +269,36 @@ class ServiceLauncher(val context: Context) { } } - fun stopService( - call: Call, - ) { + fun stopService(call: Call) { // logger.d { "stopService, call id: ${call.cid}, source: ${stopForegroundServiceSource.source}" } -// stopTelecomInternal(call, stopForegroundServiceSource) stopCallServiceInternal(call) + stopTelecomInternal(call) + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun getJetpackTelecomRepository(callId: StreamCallId): JetpackTelecomRepository { + val callsManager = CallsManager(context).apply { + // Register with the telecom interface with the supported capabilities + registerAppWithTelecom( + capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING and + CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING, + ) + } + val streamVideo = StreamVideo.instance() + val incomingCallPresenter = IncomingCallPresenter(ServiceIntentBuilder()) + val incomingCallTelecomAction = + IncomingCallTelecomAction(context, streamVideo, incomingCallPresenter) + + return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction) } -// @RequiresApi(Build.VERSION_CODES.O) -// private fun getJetpackTelecomRepository(callId: StreamCallId): JetpackTelecomRepository { -// val callsManager = CallsManager(context).apply { -// // Register with the telecom interface with the supported capabilities -// registerAppWithTelecom( -// capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING and -// CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING, -// ) -// } -// val streamVideo = StreamVideo.instance() -// val incomingCallPresenter = IncomingCallPresenter(ServiceIntentBuilder()) -// val incomingCallTelecomAction = -// IncomingCallTelecomAction(context, streamVideo, incomingCallPresenter) -// -// return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction) -// } -// -// private fun stopTelecomInternal( -// call: Call, -// stopForegroundServiceSource: StopForegroundServiceSource, -// ) { -// val telecomPermissions = TelecomPermissions() -// if (telecomPermissions.canUseTelecom(context)) { -// call.state.telecomConnection.value?.let { -// when (stopForegroundServiceSource) { -// StopForegroundServiceSource.CallAccept -> {} -// StopForegroundServiceSource.RemoveActiveCall -> { -// it.setDisconnected(DisconnectCause(DisconnectCause.CANCELED)) -// it.destroy() -// } // -// StopForegroundServiceSource.RemoveRingingCall -> {} -// StopForegroundServiceSource.SetActiveCall -> {} -// } -// } -// } -// } + private fun stopTelecomInternal(call: Call) { + val telecomPermissions = TelecomPermissions() + if (telecomPermissions.canUseTelecom(context) && telecomHelper.canUseJetpackTelecom()) { + // TODO Rahul, what to do here.... + } + } private fun stopCallServiceInternal(call: Call) { val streamVideo = StreamVideo.instanceOrNull() as? StreamVideoClient @@ -339,4 +336,3 @@ class ServiceLauncher(val context: Context) { } } } - diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt index f45e949cdc8..3dcd0abc473 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetriever.kt @@ -25,7 +25,6 @@ import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.notifications.NotificationConfig import io.getstream.video.android.core.notifications.NotificationType -import io.getstream.video.android.core.notifications.handlers.StreamDefaultNotificationHandler import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_OUTGOING_CALL diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt index 4b99325dd8f..8da09616530 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.getstream.video.android.core.notifications.internal.service import io.getstream.video.android.core.Call diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt new file mode 100644 index 00000000000..6bbe55ce0fd --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.app.Notification +import android.content.Context +import io.getstream.video.android.core.ExternalCallRejectionHandler +import io.getstream.video.android.core.ExternalCallRejectionSource +import io.getstream.video.android.core.RingingState +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.notifications.IncomingNotificationAction +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig +import io.getstream.video.android.core.notifications.internal.service.IncomingCallPresenter +import io.getstream.video.android.model.StreamCallId +import kotlinx.coroutines.launch + +class IncomingCallTelecomAction( + private val context: Context, + private val streamVideo: StreamVideo, + private val incomingCallPresenter: IncomingCallPresenter, +) { + + fun onAnswer(callId: StreamCallId) { + val pendingIntentMap = streamVideo.call(callId.type, callId.id) + .state.incomingNotificationData.pendingIntentMap + + pendingIntentMap[IncomingNotificationAction.Accept]?.send() + } + + fun onDisconnect(callId: StreamCallId) { + val call = streamVideo.call(callId.type, callId.id) + when (call.state.ringingState.value) { + is RingingState.Outgoing -> { + call.scope.launch { + val externalCallRejectionHandler = ExternalCallRejectionHandler() + externalCallRejectionHandler.onRejectCall( + ExternalCallRejectionSource.WEARABLE, + call, + streamVideo.context, + ) + } + } + + is RingingState.Active -> { + streamVideo.call(callId.type, callId.id).leave() + } + is RingingState.Incoming -> { + val pendingIntentMap = streamVideo.call(callId.type, callId.id) + .state.incomingNotificationData.pendingIntentMap + + pendingIntentMap[IncomingNotificationAction.Reject]?.send() + } + else -> {} + } + } + + fun onShowIncomingCallUi( + callId: StreamCallId, + callDisplayName: String?, + callServiceConfiguration: CallServiceConfig, + notification: Notification?, + ) { + incomingCallPresenter.showIncomingCall( + context, + callId, + callDisplayName, + callServiceConfiguration, + notification, + ) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt new file mode 100644 index 00000000000..2b6045597d2 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.Manifest +import android.net.Uri +import android.os.Build +import android.telecom.DisconnectCause +import androidx.annotation.RequiresApi +import androidx.annotation.RequiresPermission +import androidx.core.telecom.CallAttributesCompat +import androidx.core.telecom.CallControlResult +import androidx.core.telecom.CallControlScope +import androidx.core.telecom.CallsManager +import io.getstream.log.taggedLogger +import io.getstream.video.android.model.StreamCallId +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class JetpackTelecomRepository( + private val callsManager: CallsManager, + val callId: StreamCallId, + private val incomingCallTelecomAction: IncomingCallTelecomAction, +) { + private val logger by taggedLogger("JetpackTelecomRepository") + + // Keeps track of the current TelecomCall state + private val _currentCall: MutableStateFlow = MutableStateFlow(TelecomCall.None) + val currentCall = _currentCall.asStateFlow() + + /** + * Register a new call with the provided attributes. + * Use the [currentCall] StateFlow to receive status updates and process call related actions. + */ + @RequiresPermission(Manifest.permission.MANAGE_OWN_CALLS) + @RequiresApi(Build.VERSION_CODES.O) + suspend fun registerCall(displayName: String, address: Uri, isIncoming: Boolean) { + logger.d { "[registerCall]" } + // For simplicity we don't support multiple calls + if (_currentCall.value is TelecomCall.Registered) { + logger.e { "[registerCall] There cannot be more than one call at the same time." } + return + } + + // Create the call attributes + val attributes = CallAttributesCompat( + displayName = displayName, + address = address, + direction = if (isIncoming) { + CallAttributesCompat.DIRECTION_INCOMING + } else { + CallAttributesCompat.DIRECTION_OUTGOING + }, + callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL, + callCapabilities = ( + CallAttributesCompat.SUPPORTS_SET_INACTIVE + or CallAttributesCompat.SUPPORTS_STREAM + or CallAttributesCompat.SUPPORTS_TRANSFER + ), + ) + + // Creates a channel to send actions to the call scope. + val actionSource = Channel() + // Register the call and handle actions in the scope + try { + logger.d { "[registerCall] addCall" } + callsManager.addCall( + attributes, + onIsCallAnswered, // Watch needs to know if it can answer the call + onIsCallDisconnected, + onIsCallActive, + onIsCallInactive, + ) { + // Consume the actions to interact with the call inside the scope + launch { + processCallActions(actionSource.consumeAsFlow()) + } + + // Update the state to registered with default values while waiting for Telecom updates + _currentCall.value = TelecomCall.Registered( + id = getCallId(), + isActive = false, + isOnHold = false, + callAttributes = attributes, + isMuted = false, + errorCode = null, + currentCallEndpoint = null, + availableCallEndpoints = emptyList(), + actionSource = actionSource, + ) + logger.d { "[registerCall] _currentCall set to Registered" } + launch { + currentCallEndpoint.collect { + updateCurrentCall { + copy(currentCallEndpoint = it) + } + } + } + launch { + availableEndpoints.collect { + updateCurrentCall { + copy(availableCallEndpoints = it) + } + } + } + launch { + isMuted.collect { + updateCurrentCall { + copy(isMuted = it) + } + } + } + } + } catch (ex: Exception) { + logger.e(ex) { "[registerCall] exception: ${ex.message}" } + } finally { + logger.d { "[registerCall] finally" } + _currentCall.value = TelecomCall.None + } + } + + /** + * Collect the action source to handle client actions inside the call scope + */ + @RequiresApi(Build.VERSION_CODES.O) + private suspend fun CallControlScope.processCallActions(actionSource: Flow) { + actionSource.collect { action -> + logger.d { "[processCallActions]: action: $action" } + when (action) { + is TelecomCallAction.Answer -> { + doAnswer(action.isAudioCall) + } + + is TelecomCallAction.Disconnect -> { + doDisconnect(action) + } + + is TelecomCallAction.SwitchAudioEndpoint -> { + doSwitchEndpoint(action) + } + + is TelecomCallAction.TransferCall -> { + val call = _currentCall.value as? TelecomCall.Registered + val endpoints = call?.availableCallEndpoints?.firstOrNull { + it.identifier == action.endpointId + } + requestEndpointChange( + endpoint = endpoints ?: return@collect, + ) + } + + TelecomCallAction.Hold -> { + when (val result = setInactive()) { + is CallControlResult.Success -> { + onIsCallInactive() + } + + is CallControlResult.Error -> { + updateCurrentCall { + copy(errorCode = result.errorCode) + } + } + } + } + + TelecomCallAction.Activate -> { + when (val result = setActive()) { + is CallControlResult.Success -> { + logger.d { "[processCallActions] CallControlResult.Success" } + onIsCallActive() + } + + is CallControlResult.Error -> { + logger.d { "[processCallActions] CallControlResult.Error errorCode: ${result.errorCode}" } + updateCurrentCall { + copy(errorCode = result.errorCode) + } + } + } + } + + is TelecomCallAction.ToggleMute -> { + // We cannot programmatically mute the telecom stack. Instead we just update + // the state of the call and this will start/stop audio capturing. + updateCurrentCall { + copy(isMuted = !isMuted) + } + } + } + } + } + + /** + * Update the current state of our call applying the transform lambda only if the call is + * registered. Otherwise keep the current state + */ + private fun updateCurrentCall(transform: TelecomCall.Registered.() -> TelecomCall) { + _currentCall.update { call -> + if (call is TelecomCall.Registered) { + call.transform() + } else { + call + } + } + } + + @RequiresApi(Build.VERSION_CODES.O) + private suspend fun CallControlScope.doSwitchEndpoint( + action: TelecomCallAction.SwitchAudioEndpoint, + ) { + logger.d { "[doSwitchEndpoint], action: $action" } + // TODO once availableCallEndpoints is a state flow we can just get the value + val endpoints = (_currentCall.value as TelecomCall.Registered).availableCallEndpoints + + // Switch to the given endpoint or fallback to the best possible one. + val newEndpoint = endpoints.firstOrNull { it.identifier == action.endpointId } + + if (newEndpoint != null) { + requestEndpointChange(newEndpoint).also { + logger.d { "[doSwitchEndpoint] Endpoint ${newEndpoint.name} changed: $it " } + } + } + } + + private suspend fun CallControlScope.doDisconnect(action: TelecomCallAction.Disconnect) { + logger.d { "[doDisconnect] action: $action " } + disconnect(action.cause) + if (action.source != DisconnectSource.PHONE) { + onIsCallDisconnected(action.cause) + } + } + + private suspend fun CallControlScope.doAnswer(isAudioCall: Boolean) { + val callType = if (isAudioCall) { + CallAttributesCompat.CALL_TYPE_AUDIO_CALL + } else { + CallAttributesCompat.CALL_TYPE_VIDEO_CALL + } + val result = answer(callType) + + when (result) { + is CallControlResult.Success -> { + onIsCallAnswered(callType) + } + + is CallControlResult.Error -> { + updateCurrentCall { + TelecomCall.Unregistered( + id = id, + callAttributes = callAttributes, + disconnectCause = DisconnectCause(DisconnectCause.BUSY), + ) + } + } + } + } + + /** + * Can the call be successfully answered?? + * TIP: We would check the connection/call state to see if we can answer a call + * Example you may need to wait for another call to hold. + **/ + val onIsCallAnswered: suspend(type: Int) -> Unit = { + logger.d { "[onIsCallAnswered]" } + updateCurrentCall { + copy(isActive = true, isOnHold = false) + } + incomingCallTelecomAction.onAnswer(callId) + } + + /** + * Can the call perform a disconnect + */ + val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = { cause -> + logger.d { "[onIsCallDisconnected] with cause $cause" } + updateCurrentCall { + TelecomCall.Unregistered(id, callAttributes, cause) + } + incomingCallTelecomAction.onDisconnect(callId) + } + + /** + * Check is see if we can make the call active. + * Other calls and state might stop us from activating the call + */ + val onIsCallActive: suspend () -> Unit = { + logger.d { "[onIsCallActive]" } + updateCurrentCall { + copy( + errorCode = null, + isActive = true, + isOnHold = false, + ) + } + } + + /** + * Check to see if we can make the call inactivate + */ + val onIsCallInactive: suspend () -> Unit = { + logger.d { "[onIsCallInactive]" } + updateCurrentCall { + copy( + errorCode = null, + isOnHold = true, + ) + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt new file mode 100644 index 00000000000..7bb076424fc --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.model.StreamCallId + +class OutgoingCallTelecomAction(val streamVideo: StreamVideo) { + + fun onDisconnect(callId: StreamCallId) { + streamVideo.call(callId.type, callId.id).leave() + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt new file mode 100644 index 00000000000..d357764a534 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.os.ParcelUuid +import android.telecom.DisconnectCause +import androidx.core.telecom.CallAttributesCompat +import androidx.core.telecom.CallEndpointCompat +import kotlinx.coroutines.channels.Channel + +/** + * Custom representation of a call state. + */ +sealed class TelecomCall { + + /** + * There is no current or past calls in the stack + */ + object None : TelecomCall() + + /** + * Represents a registered call with the telecom stack with the values provided by the + * Telecom SDK + */ + data class Registered( + val id: ParcelUuid, + val callAttributes: CallAttributesCompat, + val isActive: Boolean, + val isOnHold: Boolean, + val isMuted: Boolean, + val errorCode: Int?, + val currentCallEndpoint: CallEndpointCompat?, + val availableCallEndpoints: List, + internal val actionSource: Channel, + ) : TelecomCall() { + + /** + * @return true if it's an incoming registered call, false otherwise + */ + fun isIncoming() = callAttributes.direction == CallAttributesCompat.DIRECTION_INCOMING + + /** + * Sends an action to the call session. It will be processed if it's still registered. + * + * @return true if the action was sent, false otherwise + */ + fun processAction(action: TelecomCallAction): Boolean { + return actionSource.trySend(action).isSuccess + } + } + + /** + * Represent a previously registered call that was disconnected + */ + data class Unregistered( + val id: ParcelUuid, + val callAttributes: CallAttributesCompat, + val disconnectCause: DisconnectCause, + ) : TelecomCall() +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt new file mode 100644 index 00000000000..b256acad2c2 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.os.ParcelUuid +import android.os.Parcelable +import android.telecom.DisconnectCause +import kotlinx.parcelize.Parcelize + +/** + * Simple interface to represent related call actions to communicate with the registered call scope + * in the [TelecomCallRepository.registerCall] + * + * Note: we are using [Parcelize] to make the actions parcelable so they can be directly used in the + * call notification. + */ +sealed interface TelecomCallAction : Parcelable { + @Parcelize + data class Answer(val isAudioCall: Boolean) : TelecomCallAction + + @Parcelize + data class Disconnect( + val cause: DisconnectCause, + val source: DisconnectSource, + ) : TelecomCallAction + + @Parcelize + object Hold : TelecomCallAction + + @Parcelize + object Activate : TelecomCallAction + + @Parcelize + data class ToggleMute(val isMute: Boolean) : TelecomCallAction + + @Parcelize + data class SwitchAudioEndpoint(val endpointId: ParcelUuid) : TelecomCallAction + + @Parcelize + data class TransferCall(val endpointId: ParcelUuid) : TelecomCallAction +} + +enum class DisconnectSource { + PHONE, WEARABLE +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt new file mode 100644 index 00000000000..5f086825c5b --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.content.Context +import android.telecom.DisconnectCause +import io.getstream.android.video.generated.models.OwnCapability +import io.getstream.video.android.core.Call + +/** + * Valid disconnected cause: [DisconnectCause.LOCAL, DisconnectCause.REMOTE, DisconnectCause.MISSED, or DisconnectCause.REJECTED] + */ +class TelecomCallController(val context: Context) { + private val telecomPermissions = TelecomPermissions() + private val telecomHelper = TelecomHelper() + + fun onRejectFromNotification(call: Call) { + onDeclineOngoingCall(call) + } + + fun leaveCurrentCall(call: Call) { + onCancelOutgoingCall(call) + } + + fun onCancelOutgoingCall(call: Call) { + if (telecomPermissions.canUseTelecom(context)) { + if (telecomHelper.canUseJetpackTelecom()) { + val jetpackTelecomCall = call.state.jetpackTelecomRepository?.currentCall?.value + jetpackTelecomCall?.let { + if (it is TelecomCall.Registered) { + it.processAction( + TelecomCallAction.Disconnect( + DisconnectCause( + DisconnectCause.LOCAL, + ), + DisconnectSource.PHONE, + ), + ) + } + } + } + } + } + + fun onDeclineOngoingCall(call: Call) { + if (telecomPermissions.canUseTelecom(context)) { + if (telecomHelper.canUseJetpackTelecom()) { + val jetpackTelecomCall = call.state.jetpackTelecomRepository?.currentCall?.value + jetpackTelecomCall?.let { + if (it is TelecomCall.Registered) { + it.processAction( + TelecomCallAction.Disconnect( + DisconnectCause( + DisconnectCause.LOCAL, + ), + DisconnectSource.PHONE, + ), + ) + } + } + } + } + } + + fun onAnswer(call: Call) { + if (telecomPermissions.canUseTelecom(context)) { + if (telecomHelper.canUseJetpackTelecom()) { + val jetpackTelecomCall = + call.state.jetpackTelecomRepository?.currentCall?.value + jetpackTelecomCall?.let { + if (it is TelecomCall.Registered) { + it.processAction(TelecomCallAction.Activate) + it.processAction(TelecomCallAction.Answer(!isVideoCall(call))) + } + } + } + } + } + + private fun isVideoCall(call: Call): Boolean { + return call.hasCapability(OwnCapability.SendVideo) || call.isVideoEnabled() + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt new file mode 100644 index 00000000000..f5ed62b2134 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +data class TelecomConfig(val schema: String, val requestPermissionOnAppLaunch: Boolean) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt new file mode 100644 index 00000000000..393e21470b3 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.os.Build + +class TelecomHelper { + + fun canUseJetpackTelecom(): Boolean { + return true && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt new file mode 100644 index 00000000000..cbd524fc905 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.telecom.TelecomManager +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import io.getstream.log.TaggedLogger +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.internal.telecom.ui.TelecomPermissionRequestActivity + +public class TelecomPermissions { + + private val logger: TaggedLogger by taggedLogger("StreamVideo:TelecomPermissions") + private val telecomHelper = TelecomHelper() + + fun getRequiredPermissionsList(): List { + val permissions = mutableListOf() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + with(permissions) { + add(android.Manifest.permission.MANAGE_OWN_CALLS) + if (!telecomHelper.canUseJetpackTelecom()) { + add(android.Manifest.permission.READ_PHONE_NUMBERS) + } + } + } + return permissions + } + + fun getRequiredPermissionsArray(): Array { + return getRequiredPermissionsList().toTypedArray() + } + + fun hasPermissions(context: Context): Boolean { + return getRequiredPermissionsArray().all { + ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED + } + } + + internal fun requestPermissions( + activity: Activity, + launcher: ActivityResultLauncher, + ) { + val intent = Intent(activity, TelecomPermissionRequestActivity::class.java) + launcher.launch(intent) + } + + fun canUseTelecom(context: Context): Boolean { + val hasTelecomConfig = (StreamVideo.instanceOrNull() as? StreamVideoClient)?.telecomConfig != null + return hasTelecomConfig && supportsTelecom(context) && hasPermissions(context) + } + + fun supportsTelecom(context: Context): Boolean { + val pm = context.packageManager + val hasTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) + + val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as? TelecomManager + val hasDefaultDialer = telecomManager?.defaultDialerPackage?.isNotEmpty() == true + + return hasTelephony && hasDefaultDialer + } + + fun requestPermissions(activity: ComponentActivity, callback: (Boolean) -> Unit) { + if (!supportsTelecom(activity)) { + logger.d { "Telecom not supported on this device" } + callback(false) + return + } + + if (hasPermissions(activity)) { + callback(true) + return + } + + // Register a launcher on the fly + val launcher: ActivityResultLauncher = + activity.activityResultRegistry.register( + "telecom_${System.currentTimeMillis()}", + ActivityResultContracts.StartActivityForResult(), + ) { result -> + val granted = result.resultCode == Activity.RESULT_OK && + (result.data?.getBooleanExtra(TelecomPermissionRequestActivity.INTENT_EXTRA_TELECOM_PERMISSION_GRANTED, false) ?: false) + logger.d { "Telecom Permission granted: $granted" } + callback(granted) + } + + val intent = Intent(activity, TelecomPermissionRequestActivity::class.java) + launcher.launch(intent) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt new file mode 100644 index 00000000000..95eed04baef --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig +import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations +import io.getstream.video.android.model.StreamCallId + +data class StartServiceParam( + val callId: StreamCallId, + val trigger: String, + val callDisplayName: String? = null, + val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, +) + +data class StopServiceParam( + val call: Call? = null, + val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt new file mode 100644 index 00000000000..25b3c42d384 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom.ui + +import android.app.Activity +import android.app.Application +import android.os.Bundle + +class ActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks { + private var activityCount: Int = 0 + + override fun onActivityCreated( + activity: Activity, + bunlde: Bundle?, + ) { // no-op + } + + override fun onActivityStarted(activity: Activity) { + if (activityCount++ == 0) { + onFirstActivityStarted(activity) + } + } + + public open fun onFirstActivityStarted(activity: Activity) { // no-op + } + + override fun onActivityResumed(activity: Activity) { // no-op + } + + override fun onActivityPaused(activity: Activity) { // no-op + } + + override fun onActivityStopped(activity: Activity) { + if (--activityCount == 0) { + onLastActivityStopped(activity) + } + } + + public open fun onLastActivityStopped(activity: Activity) { // no-op + } + + override fun onActivitySaveInstanceState( + activity: Activity, + bunlde: Bundle, + ) { // no-op + } + + override fun onActivityDestroyed(activity: Activity) { // no-op + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt new file mode 100644 index 00000000000..dcaeb25da2a --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom.ui + +import android.app.Activity +import android.app.Application +import android.widget.Toast +import androidx.activity.ComponentActivity +import io.getstream.android.push.permissions.ActivityLifecycleCallbacks +import io.getstream.android.push.permissions.R +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions + +class TelecomPermissionHandler private constructor() : ActivityLifecycleCallbacks() { + private val logger by taggedLogger("TelecomPermissionHandler") + private val telecomPermission = TelecomPermissions() + private var currentActivity: Activity? = null + + override fun onActivityStarted(activity: Activity) { + super.onActivityStarted(activity) + currentActivity = activity + } + + override fun onLastActivityStopped(activity: Activity) { + super.onLastActivityStopped(activity) + currentActivity = null + } + + override fun onFirstActivityStarted(activity: Activity) { + super.onFirstActivityStarted(activity) + if (activity is ComponentActivity) { + telecomPermission.requestPermissions(activity) { granted -> } + } + } + +// override fun onPermissionRequested() { // no-op +// } +// +// override fun onPermissionGranted() { // no-op +// } +// +// override fun onPermissionDenied() { +// logger.i { "[onPermissionDenied] currentActivity: $currentActivity" } +// currentActivity?.showNotificationBlocked() +// } +// +// override fun onPermissionRationale() { // no-op +// } + + private fun Activity.showNotificationBlocked() { + Toast.makeText( + this, + R.string.stream_push_permissions_notifications_message, + Toast.LENGTH_LONG, + ).show() + } + + public companion object { + public fun instance( + application: Application, + ): TelecomPermissionHandler = + TelecomPermissionHandler() + .also { application.registerActivityLifecycleCallbacks(it) } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt new file mode 100644 index 00000000000..5bbd907a98e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom.ui + +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import androidx.core.app.ActivityCompat +import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions + +internal class TelecomPermissionRequestActivity : Activity() { + private val REQUEST_CODE_TELECOM = 1001 + + companion object { + const val INTENT_EXTRA_TELECOM_PERMISSION_GRANTED = "telecom_permission_granted" + } + + private val telecomPermissions = TelecomPermissions() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (telecomPermissions.hasPermissions(this)) { + finishWithResult(true) + } else { + ActivityCompat.requestPermissions( + this, + telecomPermissions.getRequiredPermissionsArray(), + REQUEST_CODE_TELECOM, + ) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray, + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == REQUEST_CODE_TELECOM) { + val granted = grantResults.isNotEmpty() && grantResults.all { + it == PackageManager.PERMISSION_GRANTED + } + finishWithResult(granted) + } + } + + private fun finishWithResult(granted: Boolean) { + val resultIntent = Intent().apply { + putExtra(INTENT_EXTRA_TELECOM_PERMISSION_GRANTED, granted) + } + setResult(Activity.RESULT_OK, resultIntent) + finish() + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/CallSoundPlayer.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/CallSoundPlayer.kt index d0402f727ab..3035cf97d99 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/CallSoundPlayer.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/CallSoundPlayer.kt @@ -47,7 +47,7 @@ internal class CallSoundPlayer(private val context: Context) { } } } catch (e: Exception) { - logger.d { "[playCallSound] Error playing call sound: ${e.message}" } + logger.e(e) { "[playCallSound] Error playing call sound: ${e.message}" } } } @@ -132,7 +132,7 @@ internal class CallSoundPlayer(private val context: Context) { if (mediaPlayer.isPlaying == true) mediaPlayer.stop() } } catch (e: Exception) { - logger.d { "[stopCallSound] Error stopping call sound: ${e.message}" } + logger.e(e) { "[stopCallSound] Error stopping call sound: ${e.message}" } } finally { abandonAudioFocus() } diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt index 3cefdde79c9..ed8bfd0327b 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt @@ -59,6 +59,7 @@ import io.getstream.video.android.core.events.CallEndedSfuEvent import io.getstream.video.android.core.events.ParticipantLeftEvent import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.notifications.NotificationHandler +import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallController import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.streamCallId import io.getstream.video.android.ui.common.models.StreamCallActivityException @@ -813,6 +814,9 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper acceptOrJoinNewCall(call, onSuccess, onError) { logger.d { "Join call, ${call.cid}" } it.join() + .onSuccess { + // TODO Rahul, maybe invoke telecom + } } } @@ -831,11 +835,15 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper logger.d { "[accept] #ringing; call.cid: ${call.cid}" } acceptOrJoinNewCall(call, onSuccess, onError) { val result = call.acceptThenJoin() - result.onError { error -> - lifecycleScope.launch { - onError?.invoke(Exception(error.message)) + .onSuccess { + TelecomCallController(applicationContext) + .onAnswer(call) + } + .onError { error -> + lifecycleScope.launch { + onError?.invoke(Exception(error.message)) + } } - } result } } @@ -862,7 +870,7 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper call.state.cancelTimeout() call.state.updateRejectedBy(mutableSetOf(StreamVideo.instance().userId)) appScope.async { - val result = call.reject(reason) + val result = call.reject("activity", reason) if (lifecycleScope.isActive) { lifecycleScope.launch { result.onOutcome(call, onSuccess, onError) @@ -947,17 +955,21 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper @CallSuper public open fun onCallAction(call: Call, action: CallAction) { logger.i { "[onCallAction] #ringing; action: $action, call.cid: ${call.cid}" } + val telecomCallController = TelecomCallController(this) when (action) { is LeaveCall -> { leave(call, onSuccessFinish, onErrorFinish) + telecomCallController.leaveCurrentCall(call) } is DeclineCall -> { reject(call, RejectReason.Decline, onSuccessFinish, onErrorFinish) + telecomCallController.onDeclineOngoingCall(call) } is CancelCall -> { cancel(call, onSuccessFinish, onErrorFinish) + telecomCallController.onCancelOutgoingCall(call) } is AcceptCall -> { From faef83b66643547a69b7c4dc40399d7443d461cf Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 6 Oct 2025 19:23:11 +0530 Subject: [PATCH 03/29] phase 3: correct leaving telecom call logic --- .../api/stream-video-android-core.api | 6 +- .../io/getstream/video/android/core/Call.kt | 5 +- .../core/ExternalCallRejectionHandler.kt | 2 +- .../internal/service/ServiceLauncher.kt | 13 +--- .../telecom/JetpackTelecomRepository.kt | 4 +- .../internal/telecom/TelecomCallAction.kt | 4 +- .../internal/telecom/TelecomCallController.kt | 75 ++++++------------- .../android/ui/common/StreamCallActivity.kt | 4 - 8 files changed, 36 insertions(+), 77 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index faceb94fec2..a6ff8b820a9 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -10533,7 +10533,6 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final class io/getstream/video/android/core/notifications/internal/telecom/DisconnectSource : java/lang/Enum { public static final field PHONE Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; - public static final field WEARABLE Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; @@ -10793,11 +10792,8 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController { public fun (Landroid/content/Context;)V public final fun getContext ()Landroid/content/Context; - public final fun leaveCurrentCall (Lio/getstream/video/android/core/Call;)V + public final fun leaveCall (Lio/getstream/video/android/core/Call;)V public final fun onAnswer (Lio/getstream/video/android/core/Call;)V - public final fun onCancelOutgoingCall (Lio/getstream/video/android/core/Call;)V - public final fun onDeclineOngoingCall (Lio/getstream/video/android/core/Call;)V - public final fun onRejectFromNotification (Lio/getstream/video/android/core/Call;)V } public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 2c66b4c33f7..b3515298146 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -81,6 +81,7 @@ import io.getstream.video.android.core.model.SortField import io.getstream.video.android.core.model.UpdateUserPermissionsData import io.getstream.video.android.core.model.VideoTrack import io.getstream.video.android.core.model.toIceServer +import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallController import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.socket.common.scope.UserScope import io.getstream.video.android.core.utils.AtomicUnitCall @@ -93,7 +94,6 @@ import io.getstream.webrtc.android.ui.VideoTextureViewRenderer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -808,6 +808,9 @@ public class Call( client.state.removeRingingCall(this) } + TelecomCallController(client.context) + .leaveCall(this) + (client as StreamVideoClient).onCallCleanUp(this) cleanup() } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt index 15b822ffcb4..30d47c85941 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt @@ -60,7 +60,7 @@ internal class ExternalCallRejectionHandler() { when (source) { ExternalCallRejectionSource.NOTIFICATION -> { TelecomCallController(context) - .onRejectFromNotification(call) + .leaveCall(call) } ExternalCallRejectionSource.WEARABLE -> { /** diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index 0404c71049d..c5228308e5e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -272,34 +272,25 @@ class ServiceLauncher(val context: Context) { fun stopService(call: Call) { // logger.d { "stopService, call id: ${call.cid}, source: ${stopForegroundServiceSource.source}" } stopCallServiceInternal(call) - stopTelecomInternal(call) } @RequiresApi(Build.VERSION_CODES.O) private fun getJetpackTelecomRepository(callId: StreamCallId): JetpackTelecomRepository { val callsManager = CallsManager(context).apply { - // Register with the telecom interface with the supported capabilities registerAppWithTelecom( capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING and CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING, ) } + val streamVideo = StreamVideo.instance() val incomingCallPresenter = IncomingCallPresenter(ServiceIntentBuilder()) val incomingCallTelecomAction = IncomingCallTelecomAction(context, streamVideo, incomingCallPresenter) - + logger.d { "[getJetpackTelecomRepository] hashcode callsManager:${callsManager.hashCode()}" } return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction) } -// - private fun stopTelecomInternal(call: Call) { - val telecomPermissions = TelecomPermissions() - if (telecomPermissions.canUseTelecom(context) && telecomHelper.canUseJetpackTelecom()) { - // TODO Rahul, what to do here.... - } - } - private fun stopCallServiceInternal(call: Call) { val streamVideo = StreamVideo.instanceOrNull() as? StreamVideoClient streamVideo?.let { streamVideoClient -> diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt index 2b6045597d2..8edfc9bbcbf 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt @@ -244,8 +244,8 @@ class JetpackTelecomRepository( private suspend fun CallControlScope.doDisconnect(action: TelecomCallAction.Disconnect) { logger.d { "[doDisconnect] action: $action " } disconnect(action.cause) - if (action.source != DisconnectSource.PHONE) { - onIsCallDisconnected(action.cause) + updateCurrentCall { + TelecomCall.Unregistered(id, callAttributes, action.cause) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt index b256acad2c2..16ff1d89b6c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt @@ -23,7 +23,7 @@ import kotlinx.parcelize.Parcelize /** * Simple interface to represent related call actions to communicate with the registered call scope - * in the [TelecomCallRepository.registerCall] + * in the [JetpackTelecomRepository.registerCall] * * Note: we are using [Parcelize] to make the actions parcelable so they can be directly used in the * call notification. @@ -55,5 +55,5 @@ sealed interface TelecomCallAction : Parcelable { } enum class DisconnectSource { - PHONE, WEARABLE + PHONE, } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt index 5f086825c5b..63dea537710 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt @@ -28,70 +28,43 @@ class TelecomCallController(val context: Context) { private val telecomPermissions = TelecomPermissions() private val telecomHelper = TelecomHelper() - fun onRejectFromNotification(call: Call) { - onDeclineOngoingCall(call) - } - - fun leaveCurrentCall(call: Call) { - onCancelOutgoingCall(call) - } - - fun onCancelOutgoingCall(call: Call) { - if (telecomPermissions.canUseTelecom(context)) { - if (telecomHelper.canUseJetpackTelecom()) { - val jetpackTelecomCall = call.state.jetpackTelecomRepository?.currentCall?.value - jetpackTelecomCall?.let { - if (it is TelecomCall.Registered) { - it.processAction( - TelecomCallAction.Disconnect( - DisconnectCause( - DisconnectCause.LOCAL, - ), - DisconnectSource.PHONE, - ), - ) - } - } + fun leaveCall(call: Call) { + performAction(call) { + if (it is TelecomCall.Registered) { + it.processAction( + TelecomCallAction.Disconnect( + DisconnectCause( + DisconnectCause.LOCAL, + ), + DisconnectSource.PHONE, + ), + ) } } } - fun onDeclineOngoingCall(call: Call) { - if (telecomPermissions.canUseTelecom(context)) { - if (telecomHelper.canUseJetpackTelecom()) { - val jetpackTelecomCall = call.state.jetpackTelecomRepository?.currentCall?.value - jetpackTelecomCall?.let { - if (it is TelecomCall.Registered) { - it.processAction( - TelecomCallAction.Disconnect( - DisconnectCause( - DisconnectCause.LOCAL, - ), - DisconnectSource.PHONE, - ), - ) - } - } + fun onAnswer(call: Call) { + performAction(call) { + if (it is TelecomCall.Registered) { + it.processAction(TelecomCallAction.Activate) + it.processAction(TelecomCallAction.Answer(!isVideoCall(call))) } } } - fun onAnswer(call: Call) { + private fun isVideoCall(call: Call): Boolean { + return call.hasCapability(OwnCapability.SendVideo) || call.isVideoEnabled() + } + + private fun performAction(call: Call, block: (TelecomCall) -> Unit) { if (telecomPermissions.canUseTelecom(context)) { if (telecomHelper.canUseJetpackTelecom()) { - val jetpackTelecomCall = + val telecomCall = call.state.jetpackTelecomRepository?.currentCall?.value - jetpackTelecomCall?.let { - if (it is TelecomCall.Registered) { - it.processAction(TelecomCallAction.Activate) - it.processAction(TelecomCallAction.Answer(!isVideoCall(call))) - } + telecomCall?.let { + block(telecomCall) } } } } - - private fun isVideoCall(call: Call): Boolean { - return call.hasCapability(OwnCapability.SendVideo) || call.isVideoEnabled() - } } diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt index ed8bfd0327b..0c137a9f5ac 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt @@ -955,21 +955,17 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper @CallSuper public open fun onCallAction(call: Call, action: CallAction) { logger.i { "[onCallAction] #ringing; action: $action, call.cid: ${call.cid}" } - val telecomCallController = TelecomCallController(this) when (action) { is LeaveCall -> { leave(call, onSuccessFinish, onErrorFinish) - telecomCallController.leaveCurrentCall(call) } is DeclineCall -> { reject(call, RejectReason.Decline, onSuccessFinish, onErrorFinish) - telecomCallController.onDeclineOngoingCall(call) } is CancelCall -> { cancel(call, onSuccessFinish, onErrorFinish) - telecomCallController.onCancelOutgoingCall(call) } is AcceptCall -> { From dc50e88692ca0657ec8402cc0e7d3b6e16fa245f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 6 Oct 2025 23:11:18 +0530 Subject: [PATCH 04/29] phase 4: add video call support --- .../api/stream-video-android-core.api | 27 ++++++------- .../getstream/video/android/core/CallState.kt | 6 ++- .../internal/service/ServiceLauncher.kt | 39 ++----------------- .../telecom/JetpackTelecomRepository.kt | 13 ++++++- .../internal/telecom/TelecomCallAction.kt | 6 +-- .../internal/telecom/TelecomCallController.kt | 2 +- 6 files changed, 36 insertions(+), 57 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index a6ff8b820a9..ac83de655fc 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -10531,13 +10531,6 @@ public final class io/getstream/video/android/core/notifications/internal/servic public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/notifications/internal/telecom/DisconnectSource : java/lang/Enum { - public static final field PHONE Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; - public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; -} - public final class io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction { public fun (Landroid/content/Context;Lio/getstream/video/android/core/StreamVideo;Lio/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter;)V public final fun onAnswer (Lio/getstream/video/android/model/StreamCallId;)V @@ -10545,6 +10538,14 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final fun onShowIncomingCallUi (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Landroid/app/Notification;)V } +public final class io/getstream/video/android/core/notifications/internal/telecom/InteractionSource : java/lang/Enum { + public static final field PHONE Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; + public static final field WEARABLE Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; + public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; +} + public final class io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository { public fun (Landroidx/core/telecom/CallsManager;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction;)V public final fun getCallId ()Lio/getstream/video/android/model/StreamCallId; @@ -10553,7 +10554,7 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final fun getOnIsCallAnswered ()Lkotlin/jvm/functions/Function2; public final fun getOnIsCallDisconnected ()Lkotlin/jvm/functions/Function2; public final fun getOnIsCallInactive ()Lkotlin/jvm/functions/Function1; - public final fun registerCall (Ljava/lang/String;Landroid/net/Uri;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerCall (Ljava/lang/String;Landroid/net/Uri;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction { @@ -10686,15 +10687,15 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { public static final field CREATOR Landroid/os/Parcelable$Creator; - public fun (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource;)V + public fun (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource;)V public final fun component1 ()Landroid/telecom/DisconnectCause; - public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; - public final fun copy (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect;Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; + public final fun copy (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect;Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; public fun describeContents ()I public fun equals (Ljava/lang/Object;)Z public final fun getCause ()Landroid/telecom/DisconnectCause; - public final fun getSource ()Lio/getstream/video/android/core/notifications/internal/telecom/DisconnectSource; + public final fun getSource ()Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; public fun hashCode ()I public fun toString ()Ljava/lang/String; public fun writeToParcel (Landroid/os/Parcel;I)V diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index 58b2b6410a9..ab66ddd3d29 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -1147,8 +1147,10 @@ public class CallState( val acceptedBy = _acceptedBy.value val isAcceptedByMe = _acceptedBy.value.contains(client.userId) val createdBy = _createdBy.value - val hasActiveCall = client.state.activeCall.value != null && client.state.activeCall.value?.id == call.id - val hasRingingCall = client.state.ringingCall.value != null && client.state.ringingCall.value?.id == call.id + val hasActiveCall = + client.state.activeCall.value != null && client.state.activeCall.value?.id == call.id + val hasRingingCall = + client.state.ringingCall.value != null && client.state.ringingCall.value?.id == call.id val userIsParticipant = _session.value?.participants?.find { it.user.id == client.userId } != null val outgoingMembersCount = _members.value.filter { it.value.user.id != client.userId }.size diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index c5228308e5e..9b823e11cf8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -114,13 +114,14 @@ class ServiceLauncher(val context: Context) { val call = streamVideo.call(callId.type, callId.id) - call.state.jetpackTelecomRepository = jetpackTelecomRepository + call.state.jetpackTelecomRepository = (jetpackTelecomRepository) call.scope.launch { jetpackTelecomRepository.registerCall( formattedCallDisplayName, addressUri, true, + isVideo, ) } } @@ -144,40 +145,6 @@ class ServiceLauncher(val context: Context) { ), ) ContextCompat.startForegroundService(context, serviceIntent) - -// val callDisplayName = "ON GOING CALL NOT SET" // TODO Rahul Later -// val telecomPermissions = TelecomPermissions() -// if (telecomPermissions.canUseTelecom(context)) { -// if (telecomHelper.canUseJetpackTelecom()) { -// /** -// * Do nothing, the logic already handled in [StreamCallActivity.accept()] -// */ -// val jetpackTelecomRepository = getJetpackTelecomRepository(callId) -// -// val appSchema = streamVideo.telecomConfig?.schema -// val addressUri = "$appSchema:${callId.id}".toUri() -// val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT -// -// call.state.jetpackTelecomRepository = jetpackTelecomRepository -// -// call.scope.launch { -// jetpackTelecomRepository.registerCall( -// formattedCallDisplayName, -// addressUri, -// true, -// ) -// } -// } - // else { -// telecomServiceLauncher.addOnGoingCall( -// context, -// callId = StreamCallId(call.type, call.id), -// callDisplayName = callDisplayName, -// isVideo = call.isVideoEnabled(), -// streamVideo = streamVideo, -// ) -// } -// } } fun showOutgoingCall(call: Call, trigger: String, streamVideo: StreamVideo) { @@ -218,6 +185,7 @@ class ServiceLauncher(val context: Context) { formattedCallDisplayName, addressUri, false, + call.isVideoEnabled(), ) } launch { @@ -270,7 +238,6 @@ class ServiceLauncher(val context: Context) { } fun stopService(call: Call) { -// logger.d { "stopService, call id: ${call.cid}, source: ${stopForegroundServiceSource.source}" } stopCallServiceInternal(call) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt index 8edfc9bbcbf..5e8c4d732ce 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt @@ -53,7 +53,12 @@ class JetpackTelecomRepository( */ @RequiresPermission(Manifest.permission.MANAGE_OWN_CALLS) @RequiresApi(Build.VERSION_CODES.O) - suspend fun registerCall(displayName: String, address: Uri, isIncoming: Boolean) { + suspend fun registerCall( + displayName: String, + address: Uri, + isIncoming: Boolean, + isVideoCall: Boolean, + ) { logger.d { "[registerCall]" } // For simplicity we don't support multiple calls if (_currentCall.value is TelecomCall.Registered) { @@ -70,7 +75,11 @@ class JetpackTelecomRepository( } else { CallAttributesCompat.DIRECTION_OUTGOING }, - callType = CallAttributesCompat.CALL_TYPE_AUDIO_CALL, + callType = if (isVideoCall) { + CallAttributesCompat.CALL_TYPE_VIDEO_CALL + } else { + CallAttributesCompat.CALL_TYPE_AUDIO_CALL + }, callCapabilities = ( CallAttributesCompat.SUPPORTS_SET_INACTIVE or CallAttributesCompat.SUPPORTS_STREAM diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt index 16ff1d89b6c..0cc63b560f8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt @@ -35,7 +35,7 @@ sealed interface TelecomCallAction : Parcelable { @Parcelize data class Disconnect( val cause: DisconnectCause, - val source: DisconnectSource, + val source: InteractionSource, ) : TelecomCallAction @Parcelize @@ -54,6 +54,6 @@ sealed interface TelecomCallAction : Parcelable { data class TransferCall(val endpointId: ParcelUuid) : TelecomCallAction } -enum class DisconnectSource { - PHONE, +enum class InteractionSource { + PHONE, WEARABLE } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt index 63dea537710..61bf88e11a5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt @@ -36,7 +36,7 @@ class TelecomCallController(val context: Context) { DisconnectCause( DisconnectCause.LOCAL, ), - DisconnectSource.PHONE, + InteractionSource.PHONE, ), ) } From 0133af52a8638f0caea4d05fe7ebd94596f424d8 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 6 Oct 2025 23:24:58 +0530 Subject: [PATCH 05/29] refactor permission logic --- demo-app/src/main/AndroidManifest.xml | 11 +++ .../android/util/StreamVideoInitHelper.kt | 6 +- .../api/stream-video-android-core.api | 47 ++++------- .../src/main/AndroidManifest.xml | 1 - .../video/android/core/ClientState.kt | 9 +++ .../video/android/core/StreamVideoBuilder.kt | 16 ---- .../video/android/core/StreamVideoClient.kt | 7 -- .../internal/service/ServiceLauncher.kt | 53 ++++++------- .../telecom/JetpackTelecomRepository.kt | 2 +- .../internal/telecom/TelecomConfig.kt | 6 +- .../internal/telecom/TelecomHelper.kt | 4 +- .../internal/telecom/TelecomPermissions.kt | 79 ++++++++----------- .../telecom/ui/ActivityLifecycleCallbacks.kt | 64 --------------- .../telecom/ui/TelecomPermissionHandler.kt | 79 ------------------- .../ui/TelecomPermissionRequestActivity.kt | 70 ---------------- .../compose/permission/CallPermissions.kt | 48 ++++++++--- .../android/ui/common/StreamCallActivity.kt | 3 - 17 files changed, 141 insertions(+), 364 deletions(-) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt diff --git a/demo-app/src/main/AndroidManifest.xml b/demo-app/src/main/AndroidManifest.xml index 57a51bfeda0..beec1ae6a6f 100644 --- a/demo-app/src/main/AndroidManifest.xml +++ b/demo-app/src/main/AndroidManifest.xml @@ -24,6 +24,17 @@ android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> + + + + + + + + + + (Landroid/content/Context;)V public final fun getContext ()Landroid/content/Context; - public final fun getTelecomPermissions ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions; public final fun removeIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V public static synthetic fun removeIncomingCall$default (Lio/getstream/video/android/core/notifications/internal/service/ServiceLauncher;Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)V public final fun showIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZLjava/util/Map;Lio/getstream/video/android/core/StreamVideo;Landroid/app/Notification;)V @@ -10552,7 +10552,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final fun getCurrentCall ()Lkotlinx/coroutines/flow/StateFlow; public final fun getOnIsCallActive ()Lkotlin/jvm/functions/Function1; public final fun getOnIsCallAnswered ()Lkotlin/jvm/functions/Function2; - public final fun getOnIsCallDisconnected ()Lkotlin/jvm/functions/Function2; public final fun getOnIsCallInactive ()Lkotlin/jvm/functions/Function1; public final fun registerCall (Ljava/lang/String;Landroid/net/Uri;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -10798,13 +10797,13 @@ public final class io/getstream/video/android/core/notifications/internal/teleco } public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig { - public fun (Ljava/lang/String;Z)V + public fun (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Z - public final fun copy (Ljava/lang/String;Z)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Ljava/lang/String;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; + public final fun copy (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; public fun equals (Ljava/lang/Object;)Z - public final fun getRequestPermissionOnAppLaunch ()Z + public final fun getIntegrationType ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; public final fun getSchema ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -10815,40 +10814,22 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final fun canUseJetpackTelecom ()Z } +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType : java/lang/Enum { + public static final field JETPACK_TELECOM Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; + public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; +} + public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions { public fun ()V public final fun canUseTelecom (Landroid/content/Context;)Z public final fun getRequiredPermissionsArray ()[Ljava/lang/String; - public final fun getRequiredPermissionsList ()Ljava/util/List; + public final fun getRequiredPermissionsArray (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)[Ljava/lang/String; public final fun hasPermissions (Landroid/content/Context;)Z - public final fun requestPermissions (Landroidx/activity/ComponentActivity;Lkotlin/jvm/functions/Function1;)V public final fun supportsTelecom (Landroid/content/Context;)Z } -public final class io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks : android/app/Application$ActivityLifecycleCallbacks { - public fun ()V - public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityDestroyed (Landroid/app/Activity;)V - public fun onActivityPaused (Landroid/app/Activity;)V - public fun onActivityResumed (Landroid/app/Activity;)V - public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V - public fun onActivityStarted (Landroid/app/Activity;)V - public fun onActivityStopped (Landroid/app/Activity;)V - public fun onFirstActivityStarted (Landroid/app/Activity;)V - public fun onLastActivityStopped (Landroid/app/Activity;)V -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler : io/getstream/android/push/permissions/ActivityLifecycleCallbacks { - public static final field Companion Lio/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler$Companion; - public fun onActivityStarted (Landroid/app/Activity;)V - public fun onFirstActivityStarted (Landroid/app/Activity;)V - public fun onLastActivityStopped (Landroid/app/Activity;)V -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler$Companion { - public final fun instance (Landroid/app/Application;)Lio/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler; -} - public final class io/getstream/video/android/core/notifications/medianotifications/MediaNotificationConfig { public fun (Lio/getstream/video/android/core/notifications/medianotifications/MediaNotificationContent;Lio/getstream/video/android/core/notifications/medianotifications/MediaNotificationVisuals;Landroid/app/PendingIntent;)V public final fun component1 ()Lio/getstream/video/android/core/notifications/medianotifications/MediaNotificationContent; diff --git a/stream-video-android-core/src/main/AndroidManifest.xml b/stream-video-android-core/src/main/AndroidManifest.xml index 7138ae837a5..f82064016b7 100644 --- a/stream-video-android-core/src/main/AndroidManifest.xml +++ b/stream-video-android-core/src/main/AndroidManifest.xml @@ -116,6 +116,5 @@ android:foregroundServiceType="microphone|shortService" android:exported="false" /> - \ No newline at end of file diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 822b019c3f4..24d4940bf56 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -25,6 +25,7 @@ import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.video.android.core.notifications.internal.service.CallService import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher +import io.getstream.video.android.core.notifications.internal.telecom.TelecomIntegrationType import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.utils.safeCallWithDefault import io.getstream.video.android.model.User @@ -96,6 +97,14 @@ class ClientState(private val client: StreamVideo) { activeOrRingingCall } + fun getTelecomIntegrationType(): TelecomIntegrationType? { + return if (streamVideoClient.telecomConfig != null) { + streamVideoClient.telecomConfig.integrationType + } else { + null + } + } + /** * Handles the events for the client state. * Most event logic happens in the Call instead of the client diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index caa4ee83600..413ab7aaa15 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -16,7 +16,6 @@ package io.getstream.video.android.core -import android.app.Application import android.app.Notification import android.content.Context import androidx.lifecycle.ProcessLifecycleOwner @@ -34,7 +33,6 @@ import io.getstream.video.android.core.notifications.internal.service.CallServic import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.storage.DeviceTokenStorage import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig -import io.getstream.video.android.core.notifications.internal.telecom.ui.TelecomPermissionHandler import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.common.scope.ClientScope @@ -305,20 +303,6 @@ public class StreamVideoBuilder @JvmOverloads constructor( streamLog { "location initialized: ${location.getOrNull()}" } } - /** - * TODO Rahul Later: Ask telecom permission (maybe not needed at startup because we should have registered it in the Content provider) - */ - telecomConfig?.let { - if (it.requestPermissionOnAppLaunch) { - val app = (context.applicationContext as Application) - with(app) { - registerActivityLifecycleCallbacks( - TelecomPermissionHandler.instance(app), - ) - } - } - } - // Installs Stream Video instance StreamVideo.install(client) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index df2d47bee03..fbd3ce25ca3 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -16,7 +16,6 @@ package io.getstream.video.android.core -import android.app.Application import android.content.Context import android.media.AudioAttributes import androidx.collection.LruCache @@ -101,7 +100,6 @@ import io.getstream.video.android.core.notifications.internal.service.CallServic import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder import io.getstream.video.android.core.notifications.internal.telecom.StopServiceParam import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig -import io.getstream.video.android.core.notifications.internal.telecom.ui.TelecomPermissionHandler import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.ErrorResponse @@ -235,11 +233,6 @@ internal class StreamVideoClient internal constructor( } } activeCall?.leave() - - val app = (context.applicationContext as Application) - with(app) { - unregisterActivityLifecycleCallbacks(TelecomPermissionHandler.instance(app)) - } } /** diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index 9b823e11cf8..77a328ebebd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -63,19 +63,15 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -/** - * TODO Rahul change the name of the class its a decision maker class which will decide which - * which service to pick - */ class ServiceLauncher(val context: Context) { private val logger by taggedLogger("ServiceTriggers") private val serviceIntentBuilder = ServiceIntentBuilder() private val incomingCallPresenter = IncomingCallPresenter(serviceIntentBuilder) private val telecomHelper = TelecomHelper() - val telecomPermissions = TelecomPermissions() + private val telecomPermissions = TelecomPermissions() - @SuppressLint("MissingPermission") + @SuppressLint("MissingPermission", "NewApi") fun showIncomingCall( context: Context, callId: StreamCallId, @@ -96,33 +92,30 @@ class ServiceLauncher(val context: Context) { logger.d { "[showIncomingCall] service start result: $result" } if (telecomPermissions.canUseTelecom(context)) { if (telecomHelper.canUseJetpackTelecom()) { - // TODO Rahul, correctly use result to launch telecom when (result) { - ShowIncomingCallResult.FG_SERVICE -> {} - ShowIncomingCallResult.SERVICE -> {} - ShowIncomingCallResult.ONLY_NOTIFICATION -> {} - ShowIncomingCallResult.ERROR -> {} - } - - updateIncomingCallNotification(notification, streamVideo, callId) + ShowIncomingCallResult.FG_SERVICE -> { + updateIncomingCallNotification(notification, streamVideo, callId) - val jetpackTelecomRepository = getJetpackTelecomRepository(callId) + val jetpackTelecomRepository = getJetpackTelecomRepository(callId) - val appSchema = (streamVideo as StreamVideoClient).telecomConfig?.schema - val addressUri = "$appSchema:${callId.id}".toUri() - val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT + val appSchema = (streamVideo as StreamVideoClient).telecomConfig?.schema + val addressUri = "$appSchema:${callId.id}".toUri() + val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT - val call = streamVideo.call(callId.type, callId.id) + val call = streamVideo.call(callId.type, callId.id) - call.state.jetpackTelecomRepository = (jetpackTelecomRepository) + call.state.jetpackTelecomRepository = (jetpackTelecomRepository) - call.scope.launch { - jetpackTelecomRepository.registerCall( - formattedCallDisplayName, - addressUri, - true, - isVideo, - ) + call.scope.launch { + jetpackTelecomRepository.registerCall( + formattedCallDisplayName, + addressUri, + true, + isVideo, + ) + } + } + else -> {} } } } @@ -147,6 +140,7 @@ class ServiceLauncher(val context: Context) { ContextCompat.startForegroundService(context, serviceIntent) } + @SuppressLint("NewApi") fun showOutgoingCall(call: Call, trigger: String, streamVideo: StreamVideo) { val callConfig = (streamVideo as StreamVideoClient).callServiceConfigRegistry.get(call.type) if (!callConfig.runCallServiceInForeground) { @@ -164,7 +158,10 @@ class ServiceLauncher(val context: Context) { ContextCompat.startForegroundService(context, serviceIntent) - val callDisplayName = "NOT SET YET" // TODO Rahul + /** + * TODO We don't have api to directly render text as display name. Need more research + */ + val callDisplayName = "NOT SET YET" val telecomPermissions = TelecomPermissions() val telecomHelper = TelecomHelper() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt index 5e8c4d732ce..07361a78b53 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt @@ -299,7 +299,7 @@ class JetpackTelecomRepository( /** * Can the call perform a disconnect */ - val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = { cause -> + private val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = { cause -> logger.d { "[onIsCallDisconnected] with cause $cause" } updateCurrentCall { TelecomCall.Unregistered(id, callAttributes, cause) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt index f5ed62b2134..ac69b9d28b8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt @@ -16,4 +16,8 @@ package io.getstream.video.android.core.notifications.internal.telecom -data class TelecomConfig(val schema: String, val requestPermissionOnAppLaunch: Boolean) +data class TelecomConfig(val schema: String, val integrationType: TelecomIntegrationType) + +enum class TelecomIntegrationType { + JETPACK_TELECOM, +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt index 393e21470b3..9949fd10496 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt @@ -17,10 +17,12 @@ package io.getstream.video.android.core.notifications.internal.telecom import android.os.Build +import io.getstream.video.android.core.StreamVideo class TelecomHelper { fun canUseJetpackTelecom(): Boolean { - return true && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + val integrationTypeIsJetpack = (StreamVideo.instanceOrNull())?.state?.getTelecomIntegrationType() == TelecomIntegrationType.JETPACK_TELECOM + return integrationTypeIsJetpack && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt index cbd524fc905..e9441dd4d82 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt @@ -16,35 +16,51 @@ package io.getstream.video.android.core.notifications.internal.telecom -import android.app.Activity import android.content.Context -import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.telecom.TelecomManager -import androidx.activity.ComponentActivity -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import io.getstream.log.TaggedLogger import io.getstream.log.taggedLogger import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoClient -import io.getstream.video.android.core.notifications.internal.telecom.ui.TelecomPermissionRequestActivity -public class TelecomPermissions { +class TelecomPermissions { - private val logger: TaggedLogger by taggedLogger("StreamVideo:TelecomPermissions") + private val logger: TaggedLogger by taggedLogger("TelecomPermissions") private val telecomHelper = TelecomHelper() - fun getRequiredPermissionsList(): List { + private fun getRequiredPermissionsList(): List { val permissions = mutableListOf() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { with(permissions) { add(android.Manifest.permission.MANAGE_OWN_CALLS) if (!telecomHelper.canUseJetpackTelecom()) { - add(android.Manifest.permission.READ_PHONE_NUMBERS) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + add(android.Manifest.permission.READ_PHONE_NUMBERS) + } + add(android.Manifest.permission.CALL_PHONE) + } + } + } + return permissions + } + + private fun getRequiredPermissionsList(telecomIntegrationType: TelecomIntegrationType): List { + val permissions = mutableListOf() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + with(permissions) { + add(android.Manifest.permission.MANAGE_OWN_CALLS) + if (telecomIntegrationType == TelecomIntegrationType.JETPACK_TELECOM) { + // Do nothing + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + add(android.Manifest.permission.READ_PHONE_NUMBERS) + } + add(android.Manifest.permission.CALL_PHONE) } } } @@ -55,23 +71,20 @@ public class TelecomPermissions { return getRequiredPermissionsList().toTypedArray() } + fun getRequiredPermissionsArray(telecomIntegrationType: TelecomIntegrationType): Array { + return getRequiredPermissionsList(telecomIntegrationType).toTypedArray() + } + fun hasPermissions(context: Context): Boolean { return getRequiredPermissionsArray().all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } } - internal fun requestPermissions( - activity: Activity, - launcher: ActivityResultLauncher, - ) { - val intent = Intent(activity, TelecomPermissionRequestActivity::class.java) - launcher.launch(intent) - } + private fun optedForTelecom() = (StreamVideo.instanceOrNull() as? StreamVideoClient)?.telecomConfig != null fun canUseTelecom(context: Context): Boolean { - val hasTelecomConfig = (StreamVideo.instanceOrNull() as? StreamVideoClient)?.telecomConfig != null - return hasTelecomConfig && supportsTelecom(context) && hasPermissions(context) + return optedForTelecom() && supportsTelecom(context) && hasPermissions(context) } fun supportsTelecom(context: Context): Boolean { @@ -83,32 +96,4 @@ public class TelecomPermissions { return hasTelephony && hasDefaultDialer } - - fun requestPermissions(activity: ComponentActivity, callback: (Boolean) -> Unit) { - if (!supportsTelecom(activity)) { - logger.d { "Telecom not supported on this device" } - callback(false) - return - } - - if (hasPermissions(activity)) { - callback(true) - return - } - - // Register a launcher on the fly - val launcher: ActivityResultLauncher = - activity.activityResultRegistry.register( - "telecom_${System.currentTimeMillis()}", - ActivityResultContracts.StartActivityForResult(), - ) { result -> - val granted = result.resultCode == Activity.RESULT_OK && - (result.data?.getBooleanExtra(TelecomPermissionRequestActivity.INTENT_EXTRA_TELECOM_PERMISSION_GRANTED, false) ?: false) - logger.d { "Telecom Permission granted: $granted" } - callback(granted) - } - - val intent = Intent(activity, TelecomPermissionRequestActivity::class.java) - launcher.launch(intent) - } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt deleted file mode 100644 index 25b3c42d384..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/ActivityLifecycleCallbacks.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.notifications.internal.telecom.ui - -import android.app.Activity -import android.app.Application -import android.os.Bundle - -class ActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks { - private var activityCount: Int = 0 - - override fun onActivityCreated( - activity: Activity, - bunlde: Bundle?, - ) { // no-op - } - - override fun onActivityStarted(activity: Activity) { - if (activityCount++ == 0) { - onFirstActivityStarted(activity) - } - } - - public open fun onFirstActivityStarted(activity: Activity) { // no-op - } - - override fun onActivityResumed(activity: Activity) { // no-op - } - - override fun onActivityPaused(activity: Activity) { // no-op - } - - override fun onActivityStopped(activity: Activity) { - if (--activityCount == 0) { - onLastActivityStopped(activity) - } - } - - public open fun onLastActivityStopped(activity: Activity) { // no-op - } - - override fun onActivitySaveInstanceState( - activity: Activity, - bunlde: Bundle, - ) { // no-op - } - - override fun onActivityDestroyed(activity: Activity) { // no-op - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt deleted file mode 100644 index dcaeb25da2a..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionHandler.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.notifications.internal.telecom.ui - -import android.app.Activity -import android.app.Application -import android.widget.Toast -import androidx.activity.ComponentActivity -import io.getstream.android.push.permissions.ActivityLifecycleCallbacks -import io.getstream.android.push.permissions.R -import io.getstream.log.taggedLogger -import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions - -class TelecomPermissionHandler private constructor() : ActivityLifecycleCallbacks() { - private val logger by taggedLogger("TelecomPermissionHandler") - private val telecomPermission = TelecomPermissions() - private var currentActivity: Activity? = null - - override fun onActivityStarted(activity: Activity) { - super.onActivityStarted(activity) - currentActivity = activity - } - - override fun onLastActivityStopped(activity: Activity) { - super.onLastActivityStopped(activity) - currentActivity = null - } - - override fun onFirstActivityStarted(activity: Activity) { - super.onFirstActivityStarted(activity) - if (activity is ComponentActivity) { - telecomPermission.requestPermissions(activity) { granted -> } - } - } - -// override fun onPermissionRequested() { // no-op -// } -// -// override fun onPermissionGranted() { // no-op -// } -// -// override fun onPermissionDenied() { -// logger.i { "[onPermissionDenied] currentActivity: $currentActivity" } -// currentActivity?.showNotificationBlocked() -// } -// -// override fun onPermissionRationale() { // no-op -// } - - private fun Activity.showNotificationBlocked() { - Toast.makeText( - this, - R.string.stream_push_permissions_notifications_message, - Toast.LENGTH_LONG, - ).show() - } - - public companion object { - public fun instance( - application: Application, - ): TelecomPermissionHandler = - TelecomPermissionHandler() - .also { application.registerActivityLifecycleCallbacks(it) } - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt deleted file mode 100644 index 5bbd907a98e..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/ui/TelecomPermissionRequestActivity.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.notifications.internal.telecom.ui - -import android.app.Activity -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Bundle -import androidx.core.app.ActivityCompat -import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions - -internal class TelecomPermissionRequestActivity : Activity() { - private val REQUEST_CODE_TELECOM = 1001 - - companion object { - const val INTENT_EXTRA_TELECOM_PERMISSION_GRANTED = "telecom_permission_granted" - } - - private val telecomPermissions = TelecomPermissions() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - if (telecomPermissions.hasPermissions(this)) { - finishWithResult(true) - } else { - ActivityCompat.requestPermissions( - this, - telecomPermissions.getRequiredPermissionsArray(), - REQUEST_CODE_TELECOM, - ) - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray, - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - if (requestCode == REQUEST_CODE_TELECOM) { - val granted = grantResults.isNotEmpty() && grantResults.all { - it == PackageManager.PERMISSION_GRANTED - } - finishWithResult(granted) - } - } - - private fun finishWithResult(granted: Boolean) { - val resultIntent = Intent().apply { - putExtra(INTENT_EXTRA_TELECOM_PERMISSION_GRANTED, granted) - } - setResult(Activity.RESULT_OK, resultIntent) - finish() - } -} diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/permission/CallPermissions.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/permission/CallPermissions.kt index c5854c728c3..be2a7d91bc5 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/permission/CallPermissions.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/permission/CallPermissions.kt @@ -21,11 +21,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState import io.getstream.video.android.core.Call +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions /** * Remember call related Android permissions below: @@ -39,18 +42,7 @@ import io.getstream.video.android.core.Call @Composable public fun rememberCallPermissionsState( call: Call, - permissions: List = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - mutableListOf( - android.Manifest.permission.CAMERA, - android.Manifest.permission.RECORD_AUDIO, - android.Manifest.permission.BLUETOOTH_CONNECT, - ) - } else { - mutableListOf( - android.Manifest.permission.CAMERA, - android.Manifest.permission.RECORD_AUDIO, - ) - }, + permissions: List = getPermissions(), onPermissionsResult: ((Map) -> Unit)? = null, onAllPermissionsGranted: (suspend () -> Unit)? = null, ): VideoPermissionsState { @@ -93,6 +85,38 @@ public fun rememberCallPermissionsState( } } +@Composable +private fun getPermissions(): List { + val context = LocalContext.current + val permissionsList = mutableListOf() + val telecomPermissions = TelecomPermissions() + + if (telecomPermissions.supportsTelecom(context)) { + val telecomIntegrationType = StreamVideo.instanceOrNull()?.state?.getTelecomIntegrationType() + telecomIntegrationType?.let { + permissionsList.addAll( + telecomPermissions.getRequiredPermissionsArray(telecomIntegrationType), + ) + } + } + + permissionsList.addAll( + mutableListOf( + android.Manifest.permission.CAMERA, + android.Manifest.permission.RECORD_AUDIO, + ), + ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + permissionsList.addAll( + mutableListOf( + android.Manifest.permission.BLUETOOTH_CONNECT, + ), + ) + } + return permissionsList +} + /** * Lunch call permissions about: * diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt index 0c137a9f5ac..9d33cd87026 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt @@ -814,9 +814,6 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper acceptOrJoinNewCall(call, onSuccess, onError) { logger.d { "Join call, ${call.cid}" } it.join() - .onSuccess { - // TODO Rahul, maybe invoke telecom - } } } From dbc14a1f49591e2888becb41d6cb523551aa501f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 10:47:17 +0530 Subject: [PATCH 06/29] refactor permissions from manifest --- demo-app/src/main/AndroidManifest.xml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/demo-app/src/main/AndroidManifest.xml b/demo-app/src/main/AndroidManifest.xml index beec1ae6a6f..57a51bfeda0 100644 --- a/demo-app/src/main/AndroidManifest.xml +++ b/demo-app/src/main/AndroidManifest.xml @@ -24,17 +24,6 @@ android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> - - - - - - - - - - Date: Tue, 7 Oct 2025 10:47:55 +0530 Subject: [PATCH 07/29] update androidx-telecom --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84c05722b02..dcf85fd486d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -226,7 +226,7 @@ androidx-camera-view = { group = "androidx.camera", name = "camera-view", versio androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version = "1.3.0" } zxing-core = { group = "com.google.zxing", name = "core", version = "3.5.2" } #jetpack telecom -androidx-telecom = { group = "androidx.core", name = "core-telecom", version = "1.0.0" } +androidx-telecom = { group = "androidx.core", name = "core-telecom", version = "1.0.1" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From 59046ac6a9219f06deb60cdecc89914e01659806 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 10:54:06 +0530 Subject: [PATCH 08/29] Mark classes as internal/private --- .../api/stream-video-android-core.api | 275 ------------------ .../getstream/video/android/core/CallState.kt | 2 +- .../video/android/core/ClientState.kt | 2 +- .../internal/service/IncomingCallPresenter.kt | 4 +- .../internal/service/ServiceIntentBuilder.kt | 2 +- .../internal/service/ServiceLauncher.kt | 2 +- .../internal/service/StartServiceParam.kt | 9 +- .../telecom/IncomingCallTelecomAction.kt | 2 +- .../telecom/JetpackTelecomRepository.kt | 4 +- .../telecom/OutgoingCallTelecomAction.kt | 27 -- .../internal/telecom/TelecomCall.kt | 2 +- .../internal/telecom/TelecomCallAction.kt | 4 +- .../internal/telecom/TelecomHelper.kt | 2 +- .../internal/telecom/TelecomPermissions.kt | 4 +- 14 files changed, 21 insertions(+), 320 deletions(-) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index d1c4fadd99c..60ce5400875 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -5759,7 +5759,6 @@ public final class io/getstream/video/android/core/ClientState { public final fun getCallConfigRegistry ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry; public final fun getConnection ()Lkotlinx/coroutines/flow/StateFlow; public final fun getRingingCall ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getServiceLauncher ()Lio/getstream/video/android/core/notifications/internal/service/ServiceLauncher; public final fun getTelecomIntegrationType ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; public final fun getUser ()Lkotlinx/coroutines/flow/StateFlow; public final fun handleError (Lio/getstream/result/Error;)V @@ -10433,135 +10432,6 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final fun getLivestreamGuestCallServiceConfig ()Ljava/util/Map; } -public final class io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter { - public fun (Lio/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder;)V - public final fun showIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Landroid/app/Notification;)Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; -} - -public final class io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder { - public fun ()V - public final fun buildStartIntent (Landroid/content/Context;Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam;)Landroid/content/Intent; - public final fun buildStopIntent (Landroid/content/Context;Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam;)Landroid/content/Intent; -} - -public final class io/getstream/video/android/core/notifications/internal/service/ServiceLauncher { - public fun (Landroid/content/Context;)V - public final fun getContext ()Landroid/content/Context; - public final fun removeIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V - public static synthetic fun removeIncomingCall$default (Lio/getstream/video/android/core/notifications/internal/service/ServiceLauncher;Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)V - public final fun showIncomingCall (Landroid/content/Context;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZLjava/util/Map;Lio/getstream/video/android/core/StreamVideo;Landroid/app/Notification;)V - public final fun showOnGoingCall (Lio/getstream/video/android/core/Call;Ljava/lang/String;Lio/getstream/video/android/core/StreamVideo;)V - public final fun showOutgoingCall (Lio/getstream/video/android/core/Call;Ljava/lang/String;Lio/getstream/video/android/core/StreamVideo;)V - public final fun stopService (Lio/getstream/video/android/core/Call;)V -} - -public final class io/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult : java/lang/Enum { - public static final field ERROR Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; - public static final field FG_SERVICE Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; - public static final field ONLY_NOTIFICATION Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; - public static final field SERVICE Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; - public static fun values ()[Lio/getstream/video/android/core/notifications/internal/service/ShowIncomingCallResult; -} - -public final class io/getstream/video/android/core/notifications/internal/service/StartServiceParam { - public fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V - public synthetic fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/getstream/video/android/model/StreamCallId; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public final fun copy (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/service/StartServiceParam; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/StartServiceParam;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/StartServiceParam; - public fun equals (Ljava/lang/Object;)Z - public final fun getCallDisplayName ()Ljava/lang/String; - public final fun getCallId ()Lio/getstream/video/android/model/StreamCallId; - public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public final fun getTrigger ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getSource ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$CallAccept : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { - public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$CallAccept; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveActiveCall : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { - public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveActiveCall; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveRingingCall : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { - public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$RemoveRingingCall; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$SetActiveCall : io/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource { - public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/service/StopForegroundServiceSource$SetActiveCall; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/service/StopServiceParam { - public fun ()V - public fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V - public synthetic fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/getstream/video/android/core/Call; - public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public final fun copy (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/service/StopServiceParam; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/StopServiceParam;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/StopServiceParam; - public fun equals (Ljava/lang/Object;)Z - public final fun getCall ()Lio/getstream/video/android/core/Call; - public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction { - public fun (Landroid/content/Context;Lio/getstream/video/android/core/StreamVideo;Lio/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter;)V - public final fun onAnswer (Lio/getstream/video/android/model/StreamCallId;)V - public final fun onDisconnect (Lio/getstream/video/android/model/StreamCallId;)V - public final fun onShowIncomingCallUi (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Landroid/app/Notification;)V -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/InteractionSource : java/lang/Enum { - public static final field PHONE Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; - public static final field WEARABLE Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; - public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository { - public fun (Landroidx/core/telecom/CallsManager;Lio/getstream/video/android/model/StreamCallId;Lio/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction;)V - public final fun getCallId ()Lio/getstream/video/android/model/StreamCallId; - public final fun getCurrentCall ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getOnIsCallActive ()Lkotlin/jvm/functions/Function1; - public final fun getOnIsCallAnswered ()Lkotlin/jvm/functions/Function2; - public final fun getOnIsCallInactive ()Lkotlin/jvm/functions/Function1; - public final fun registerCall (Ljava/lang/String;Landroid/net/Uri;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction { - public fun (Lio/getstream/video/android/core/StreamVideo;)V - public final fun getStreamVideo ()Lio/getstream/video/android/core/StreamVideo; - public final fun onDisconnect (Lio/getstream/video/android/model/StreamCallId;)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/StartServiceParam { public fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V public synthetic fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -10595,65 +10465,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public fun toString ()Ljava/lang/String; } -public abstract class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall$None : io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { - public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$None; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered : io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { - public fun (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;ZZZLjava/lang/Integer;Landroidx/core/telecom/CallEndpointCompat;Ljava/util/List;Lkotlinx/coroutines/channels/Channel;)V - public final fun component1 ()Landroid/os/ParcelUuid; - public final fun component2 ()Landroidx/core/telecom/CallAttributesCompat; - public final fun component3 ()Z - public final fun component4 ()Z - public final fun component5 ()Z - public final fun component6 ()Ljava/lang/Integer; - public final fun component7 ()Landroidx/core/telecom/CallEndpointCompat; - public final fun component8 ()Ljava/util/List; - public final fun copy (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;ZZZLjava/lang/Integer;Landroidx/core/telecom/CallEndpointCompat;Ljava/util/List;Lkotlinx/coroutines/channels/Channel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered;Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;ZZZLjava/lang/Integer;Landroidx/core/telecom/CallEndpointCompat;Ljava/util/List;Lkotlinx/coroutines/channels/Channel;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Registered; - public fun equals (Ljava/lang/Object;)Z - public final fun getAvailableCallEndpoints ()Ljava/util/List; - public final fun getCallAttributes ()Landroidx/core/telecom/CallAttributesCompat; - public final fun getCurrentCallEndpoint ()Landroidx/core/telecom/CallEndpointCompat; - public final fun getErrorCode ()Ljava/lang/Integer; - public final fun getId ()Landroid/os/ParcelUuid; - public fun hashCode ()I - public final fun isActive ()Z - public final fun isIncoming ()Z - public final fun isMuted ()Z - public final fun isOnHold ()Z - public final fun processAction (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction;)Z - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered : io/getstream/video/android/core/notifications/internal/telecom/TelecomCall { - public fun (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;Landroid/telecom/DisconnectCause;)V - public final fun component1 ()Landroid/os/ParcelUuid; - public final fun component2 ()Landroidx/core/telecom/CallAttributesCompat; - public final fun component3 ()Landroid/telecom/DisconnectCause; - public final fun copy (Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;Landroid/telecom/DisconnectCause;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered;Landroid/os/ParcelUuid;Landroidx/core/telecom/CallAttributesCompat;Landroid/telecom/DisconnectCause;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCall$Unregistered; - public fun equals (Ljava/lang/Object;)Z - public final fun getCallAttributes ()Landroidx/core/telecom/CallAttributesCompat; - public final fun getDisconnectCause ()Landroid/telecom/DisconnectCause; - public final fun getId ()Landroid/os/ParcelUuid; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction : android/os/Parcelable { -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; - public fun describeContents ()I - public fun writeToParcel (Landroid/os/Parcel;I)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; @@ -10662,20 +10473,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public fun (Z)V - public final fun component1 ()Z - public final fun copy (Z)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; - public fun describeContents ()I - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public final fun isAudioCall ()Z - public fun toString ()Ljava/lang/String; - public fun writeToParcel (Landroid/os/Parcel;I)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; @@ -10684,22 +10481,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public fun (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource;)V - public final fun component1 ()Landroid/telecom/DisconnectCause; - public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; - public final fun copy (Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect;Landroid/telecom/DisconnectCause;Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; - public fun describeContents ()I - public fun equals (Ljava/lang/Object;)Z - public final fun getCause ()Landroid/telecom/DisconnectCause; - public final fun getSource ()Lio/getstream/video/android/core/notifications/internal/telecom/InteractionSource; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public fun writeToParcel (Landroid/os/Parcel;I)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; @@ -10708,13 +10489,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public static final field INSTANCE Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold; - public fun describeContents ()I - public fun writeToParcel (Landroid/os/Parcel;I)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold; @@ -10723,20 +10497,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public fun (Landroid/os/ParcelUuid;)V - public final fun component1 ()Landroid/os/ParcelUuid; - public final fun copy (Landroid/os/ParcelUuid;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint;Landroid/os/ParcelUuid;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; - public fun describeContents ()I - public fun equals (Ljava/lang/Object;)Z - public final fun getEndpointId ()Landroid/os/ParcelUuid; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public fun writeToParcel (Landroid/os/Parcel;I)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; @@ -10745,20 +10505,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public fun (Z)V - public final fun component1 ()Z - public final fun copy (Z)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; - public fun describeContents ()I - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public final fun isMute ()Z - public fun toString ()Ljava/lang/String; - public fun writeToParcel (Landroid/os/Parcel;I)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; @@ -10767,20 +10513,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall : io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction { - public static final field CREATOR Landroid/os/Parcelable$Creator; - public fun (Landroid/os/ParcelUuid;)V - public final fun component1 ()Landroid/os/ParcelUuid; - public final fun copy (Landroid/os/ParcelUuid;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall;Landroid/os/ParcelUuid;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; - public fun describeContents ()I - public fun equals (Ljava/lang/Object;)Z - public final fun getEndpointId ()Landroid/os/ParcelUuid; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public fun writeToParcel (Landroid/os/Parcel;I)V -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; @@ -10809,11 +10541,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper { - public fun ()V - public final fun canUseJetpackTelecom ()Z -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType : java/lang/Enum { public static final field JETPACK_TELECOM Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; public static fun getEntries ()Lkotlin/enums/EnumEntries; @@ -10824,9 +10551,7 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions { public fun ()V public final fun canUseTelecom (Landroid/content/Context;)Z - public final fun getRequiredPermissionsArray ()[Ljava/lang/String; public final fun getRequiredPermissionsArray (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)[Ljava/lang/String; - public final fun hasPermissions (Landroid/content/Context;)Z public final fun supportsTelecom (Landroid/content/Context;)Z } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index ab66ddd3d29..01fa1102b94 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -696,7 +696,7 @@ public class CallState( AtomicReference(null) @InternalStreamVideoApi - var jetpackTelecomRepository: JetpackTelecomRepository? = null + internal var jetpackTelecomRepository: JetpackTelecomRepository? = null internal var incomingNotificationData = IncomingNotificationData(emptyMap()) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 24d4940bf56..d2776edeedc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -84,7 +84,7 @@ class ClientState(private val client: StreamVideo) { public val activeCall: StateFlow = _activeCall public val callConfigRegistry = (client as StreamVideoClient).callServiceConfigRegistry - val serviceLauncher = ServiceLauncher(client.context) + private val serviceLauncher = ServiceLauncher(client.context) /** * Returns true if there is an active or ringing call diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt index 54dd1ac3951..0df1e9c41ea 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt @@ -30,7 +30,7 @@ import io.getstream.video.android.core.notifications.internal.telecom.StartServi import io.getstream.video.android.core.utils.safeCallWithResult import io.getstream.video.android.model.StreamCallId -class IncomingCallPresenter(private val serviceIntentBuilder: ServiceIntentBuilder) { +internal class IncomingCallPresenter(private val serviceIntentBuilder: ServiceIntentBuilder) { private val logger by taggedLogger("IncomingCallPresenter") fun showIncomingCall( @@ -109,6 +109,6 @@ class IncomingCallPresenter(private val serviceIntentBuilder: ServiceIntentBuild } } -enum class ShowIncomingCallResult { +internal enum class ShowIncomingCallResult { FG_SERVICE, SERVICE, ONLY_NOTIFICATION, ERROR } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt index 0663b27b1be..6c92a4d3ed0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt @@ -33,7 +33,7 @@ import io.getstream.video.android.core.notifications.internal.telecom.StopServic import io.getstream.video.android.core.utils.safeCallWithDefault import io.getstream.video.android.model.StreamCallId -class ServiceIntentBuilder { +internal class ServiceIntentBuilder { private val logger by taggedLogger("TelecomIntentBuilder") diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index 77a328ebebd..1cf6e2b01a7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -63,7 +63,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -class ServiceLauncher(val context: Context) { +internal class ServiceLauncher(val context: Context) { private val logger by taggedLogger("ServiceTriggers") private val serviceIntentBuilder = ServiceIntentBuilder() diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt index 8da09616530..5174e294443 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt @@ -19,19 +19,22 @@ package io.getstream.video.android.core.notifications.internal.service import io.getstream.video.android.core.Call import io.getstream.video.android.model.StreamCallId -data class StartServiceParam( +/** + * Will be used when integrating Telecom Platform API + */ +private data class StartServiceParam( val callId: StreamCallId, val trigger: String, val callDisplayName: String? = null, val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, ) -data class StopServiceParam( +private data class StopServiceParam( val call: Call? = null, val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, ) -sealed class StopForegroundServiceSource(val source: String) { +private sealed class StopForegroundServiceSource(val source: String) { data object CallAccept : StopForegroundServiceSource("accept the call") data object SetActiveCall : StopForegroundServiceSource("set active call") data object RemoveActiveCall : StopForegroundServiceSource("remove active call") diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt index 6bbe55ce0fd..49c8881c98b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt @@ -28,7 +28,7 @@ import io.getstream.video.android.core.notifications.internal.service.IncomingCa import io.getstream.video.android.model.StreamCallId import kotlinx.coroutines.launch -class IncomingCallTelecomAction( +internal class IncomingCallTelecomAction( private val context: Context, private val streamVideo: StreamVideo, private val incomingCallPresenter: IncomingCallPresenter, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt index 07361a78b53..60cdceffe98 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt @@ -36,9 +36,9 @@ import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -class JetpackTelecomRepository( +internal class JetpackTelecomRepository( private val callsManager: CallsManager, - val callId: StreamCallId, + private val callId: StreamCallId, private val incomingCallTelecomAction: IncomingCallTelecomAction, ) { private val logger by taggedLogger("JetpackTelecomRepository") diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt deleted file mode 100644 index 7bb076424fc..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/OutgoingCallTelecomAction.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.notifications.internal.telecom - -import io.getstream.video.android.core.StreamVideo -import io.getstream.video.android.model.StreamCallId - -class OutgoingCallTelecomAction(val streamVideo: StreamVideo) { - - fun onDisconnect(callId: StreamCallId) { - streamVideo.call(callId.type, callId.id).leave() - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt index d357764a534..848f0104857 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt @@ -25,7 +25,7 @@ import kotlinx.coroutines.channels.Channel /** * Custom representation of a call state. */ -sealed class TelecomCall { +internal sealed class TelecomCall { /** * There is no current or past calls in the stack diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt index 0cc63b560f8..1dc6ecba49f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt @@ -28,7 +28,7 @@ import kotlinx.parcelize.Parcelize * Note: we are using [Parcelize] to make the actions parcelable so they can be directly used in the * call notification. */ -sealed interface TelecomCallAction : Parcelable { +internal sealed interface TelecomCallAction : Parcelable { @Parcelize data class Answer(val isAudioCall: Boolean) : TelecomCallAction @@ -54,6 +54,6 @@ sealed interface TelecomCallAction : Parcelable { data class TransferCall(val endpointId: ParcelUuid) : TelecomCallAction } -enum class InteractionSource { +internal enum class InteractionSource { PHONE, WEARABLE } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt index 9949fd10496..bcc462a123c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomHelper.kt @@ -19,7 +19,7 @@ package io.getstream.video.android.core.notifications.internal.telecom import android.os.Build import io.getstream.video.android.core.StreamVideo -class TelecomHelper { +internal class TelecomHelper { fun canUseJetpackTelecom(): Boolean { val integrationTypeIsJetpack = (StreamVideo.instanceOrNull())?.state?.getTelecomIntegrationType() == TelecomIntegrationType.JETPACK_TELECOM diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt index e9441dd4d82..6b82e562072 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt @@ -67,7 +67,7 @@ class TelecomPermissions { return permissions } - fun getRequiredPermissionsArray(): Array { + private fun getRequiredPermissionsArray(): Array { return getRequiredPermissionsList().toTypedArray() } @@ -75,7 +75,7 @@ class TelecomPermissions { return getRequiredPermissionsList(telecomIntegrationType).toTypedArray() } - fun hasPermissions(context: Context): Boolean { + private fun hasPermissions(context: Context): Boolean { return getRequiredPermissionsArray().all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } From 94b7608dfe7e57f25bbca9b1519e4d5fd1410b7b Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 11:26:24 +0530 Subject: [PATCH 09/29] Mark debugging apis as internal --- .../api/stream-video-android-core.api | 4 ++-- .../kotlin/io/getstream/video/android/core/Call.kt | 13 +++++++++++-- .../notifications/internal/service/CallService.kt | 5 ++++- .../video/android/ui/common/StreamCallActivity.kt | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 60ce5400875..e0a1faaefb1 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -5513,8 +5513,8 @@ public final class io/getstream/video/android/core/Call { public final fun processAudioSample (Lorg/webrtc/audio/JavaAudioDeviceModule$AudioSamples;)V public final fun queryMembers (Ljava/util/Map;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun queryMembers$default (Lio/getstream/video/android/core/Call;Ljava/util/Map;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun reject (Ljava/lang/String;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun reject$default (Lio/getstream/video/android/core/Call;Ljava/lang/String;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun reject (Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun reject$default (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun rejoin (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun removeMembers (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun requestPermissions ([Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index b3515298146..071e60b5654 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -1348,11 +1348,20 @@ public class Call( return clientImpl.accept(type, id) } - suspend fun reject(source: String = "n/a", reason: RejectReason? = null): Result { - logger.d { "[reject] source: $source, #ringing; rejectReason: $reason, call_id:$id" } + suspend fun reject(reason: RejectReason? = null): Result { + logger.d { "[reject] #ringing; rejectReason: $reason, call_id:$id" } return clientImpl.reject(type, id, reason) } + // For debugging + internal suspend fun reject( + source: String = "n/a", + reason: RejectReason? = null, + ): Result { + logger.d { "[reject] source: $source" } + return reject(reason) + } + fun processAudioSample(audioSample: AudioSamples) { soundInputProcessor.processSoundInput(audioSample.data) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index f0152ad4f5c..9e805a7fb5d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -891,7 +891,10 @@ internal open class CallService : Service() { if (ringingState is RingingState.Outgoing) { // If I'm calling, end the call for everyone serviceScope.launch { - call.reject("noob", RejectReason.Custom("Android Service Task Removed")) + call.reject( + "CallService.EndCall", + RejectReason.Custom("Android Service Task Removed"), + ) logger.i { "[onTaskRemoved] Ended outgoing call for all users." } } } else if (ringingState is RingingState.Incoming) { diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt index 9d33cd87026..b2622eba46b 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt @@ -867,7 +867,7 @@ public abstract class StreamCallActivity : ComponentActivity(), ActivityCallOper call.state.cancelTimeout() call.state.updateRejectedBy(mutableSetOf(StreamVideo.instance().userId)) appScope.async { - val result = call.reject("activity", reason) + val result = call.reject(reason) if (lifecycleScope.isActive) { lifecycleScope.launch { result.onOutcome(call, onSuccess, onError) From b230b70163e74716a13bba989b5df3567525dc89 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 11:27:29 +0530 Subject: [PATCH 10/29] Mark apis as internal --- .../api/stream-video-android-core.api | 8 -------- .../video/android/core/ExternalCallRejectionHandler.kt | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index e0a1faaefb1..2fc1e431fe3 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -5871,14 +5871,6 @@ public final class io/getstream/video/android/core/EventSubscription { public final fun setDisposed (Z)V } -public final class io/getstream/video/android/core/ExternalCallRejectionSource : java/lang/Enum { - public static final field NOTIFICATION Lio/getstream/video/android/core/ExternalCallRejectionSource; - public static final field WEARABLE Lio/getstream/video/android/core/ExternalCallRejectionSource; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/ExternalCallRejectionSource; - public static fun values ()[Lio/getstream/video/android/core/ExternalCallRejectionSource; -} - public abstract class io/getstream/video/android/core/GEO { } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt index 30d47c85941..4ec18be970b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ExternalCallRejectionHandler.kt @@ -74,6 +74,6 @@ internal class ExternalCallRejectionHandler() { } } -enum class ExternalCallRejectionSource { +internal enum class ExternalCallRejectionSource { NOTIFICATION, WEARABLE } From 521023e950634ec29cd7172ea724d99a4a2d3285 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 11:31:54 +0530 Subject: [PATCH 11/29] Mark apis as internal --- .../api/stream-video-android-core.api | 33 --------------- .../internal/service/StartServiceParam.kt | 42 ------------------- .../internal/telecom/TelecomServiceParam.kt | 4 +- 3 files changed, 2 insertions(+), 77 deletions(-) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 2fc1e431fe3..3eda7bd4bfd 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -10424,39 +10424,6 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final fun getLivestreamGuestCallServiceConfig ()Ljava/util/Map; } -public final class io/getstream/video/android/core/notifications/internal/telecom/StartServiceParam { - public fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V - public synthetic fun (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/getstream/video/android/model/StreamCallId; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public final fun copy (Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam;Lio/getstream/video/android/model/StreamCallId;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/StartServiceParam; - public fun equals (Ljava/lang/Object;)Z - public final fun getCallDisplayName ()Ljava/lang/String; - public final fun getCallId ()Lio/getstream/video/android/model/StreamCallId; - public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public final fun getTrigger ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/StopServiceParam { - public fun ()V - public fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)V - public synthetic fun (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/getstream/video/android/core/Call; - public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public final fun copy (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;)Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam;Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/StopServiceParam; - public fun equals (Ljava/lang/Object;)Z - public final fun getCall ()Lio/getstream/video/android/core/Call; - public final fun getCallServiceConfiguration ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate$Creator : android/os/Parcelable$Creator { public fun ()V public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt deleted file mode 100644 index 5174e294443..00000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StartServiceParam.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.notifications.internal.service - -import io.getstream.video.android.core.Call -import io.getstream.video.android.model.StreamCallId - -/** - * Will be used when integrating Telecom Platform API - */ -private data class StartServiceParam( - val callId: StreamCallId, - val trigger: String, - val callDisplayName: String? = null, - val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, -) - -private data class StopServiceParam( - val call: Call? = null, - val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, -) - -private sealed class StopForegroundServiceSource(val source: String) { - data object CallAccept : StopForegroundServiceSource("accept the call") - data object SetActiveCall : StopForegroundServiceSource("set active call") - data object RemoveActiveCall : StopForegroundServiceSource("remove active call") - data object RemoveRingingCall : StopForegroundServiceSource("remove ringing call") -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt index 95eed04baef..a78a4842c16 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt @@ -21,14 +21,14 @@ import io.getstream.video.android.core.notifications.internal.service.CallServic import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations import io.getstream.video.android.model.StreamCallId -data class StartServiceParam( +internal data class StartServiceParam( val callId: StreamCallId, val trigger: String, val callDisplayName: String? = null, val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, ) -data class StopServiceParam( +internal data class StopServiceParam( val call: Call? = null, val callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, ) From 411f41ec177c0a7ef1222ccdb8895798eb55e39d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 11:36:26 +0530 Subject: [PATCH 12/29] Remove unused internal apis from CallService --- .../internal/service/CallService.kt | 190 ------------------ 1 file changed, 190 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index 9e805a7fb5d..2c93d4614fb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -18,11 +18,8 @@ package io.getstream.video.android.core.notifications.internal.service import android.Manifest import android.annotation.SuppressLint -import android.app.ActivityManager import android.app.Notification import android.app.Service -import android.content.ComponentName -import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager @@ -30,13 +27,11 @@ import android.content.pm.ServiceInfo import android.os.IBinder import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat -import androidx.core.content.ContextCompat import androidx.media.session.MediaButtonReceiver import io.getstream.android.video.generated.models.CallAcceptedEvent import io.getstream.android.video.generated.models.CallEndedEvent import io.getstream.android.video.generated.models.CallRejectedEvent import io.getstream.android.video.generated.models.LocalCallMissedEvent -import io.getstream.log.StreamLog import io.getstream.log.taggedLogger import io.getstream.video.android.core.Call import io.getstream.video.android.core.RealtimeConnection @@ -54,8 +49,6 @@ import io.getstream.video.android.core.notifications.internal.receivers.ToggleCa import io.getstream.video.android.core.socket.common.scope.ClientScope import io.getstream.video.android.core.sounds.CallSoundPlayer import io.getstream.video.android.core.utils.safeCall -import io.getstream.video.android.core.utils.safeCallWithDefault -import io.getstream.video.android.core.utils.safeCallWithResult import io.getstream.video.android.core.utils.startForegroundWithServiceType import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.streamCallDisplayName @@ -106,189 +99,6 @@ internal open class CallService : Service() { const val TRIGGER_OUTGOING_CALL = "outgoing_call" const val TRIGGER_ONGOING_CALL = "ongoing_call" const val EXTRA_STOP_SERVICE = "io.getstream.video.android.core.stop_service" - - /** - * Build start intent. - * - * @param context the context. - * @param callId the call id. - * @param trigger one of [TRIGGER_INCOMING_CALL], [TRIGGER_OUTGOING_CALL] or [TRIGGER_ONGOING_CALL] - * @param callDisplayName the display name. - */ - @Deprecated("", level = DeprecationLevel.WARNING) - fun buildStartIntent( - context: Context, - callId: StreamCallId, - trigger: String, - callDisplayName: String? = null, - callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, - ): Intent { - if (true) throw RuntimeException("Kyun aagaya idhar?") - val serviceClass = callServiceConfiguration.serviceClass - StreamLog.i(TAG) { "Resolved service class: $serviceClass" } - val serviceIntent = Intent(context, serviceClass) - serviceIntent.putExtra(INTENT_EXTRA_CALL_CID, callId) - - when (trigger) { - TRIGGER_INCOMING_CALL -> { - serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_INCOMING_CALL) - serviceIntent.putExtra(INTENT_EXTRA_CALL_DISPLAY_NAME, callDisplayName) - } - - TRIGGER_OUTGOING_CALL -> { - serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_OUTGOING_CALL) - } - - TRIGGER_ONGOING_CALL -> { - serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_ONGOING_CALL) - } - - TRIGGER_REMOVE_INCOMING_CALL -> { - serviceIntent.putExtra(TRIGGER_KEY, TRIGGER_REMOVE_INCOMING_CALL) - } - - else -> { - throw IllegalArgumentException( - "Unknown $trigger, must be one of: $TRIGGER_INCOMING_CALL, $TRIGGER_OUTGOING_CALL, $TRIGGER_ONGOING_CALL", - ) - } - } - StreamLog.d(TAG) { "[buildStartIntent], call_id:${callId.cid}" } - return serviceIntent - } - - /** - * Build stop intent. - * - * @param context the context. - */ - @Deprecated("", level = DeprecationLevel.ERROR) - fun buildStopIntent( - context: Context, - call: Call? = null, - callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, - ) = safeCallWithDefault(Intent(context, CallService::class.java)) { - val serviceClass = callServiceConfiguration.serviceClass - - val intent = if (isServiceRunning(context, serviceClass)) { - Intent(context, serviceClass) - } else { - Intent(context, CallService::class.java) - } - call?.let { - StreamLog.d(TAG) { "[buildStopIntent], call_id:${call.cid}" } - val streamCallId = StreamCallId(call.type, call.id, call.cid) - intent.putExtra(INTENT_EXTRA_CALL_CID, streamCallId) - } - intent.putExtra(EXTRA_STOP_SERVICE, true) - } - - @Deprecated("", level = DeprecationLevel.ERROR) - fun showIncomingCall( - context: Context, - callId: StreamCallId, - callDisplayName: String?, - callServiceConfiguration: CallServiceConfig = DefaultCallConfigurations.default, - notification: Notification?, - ) { - StreamLog.d(TAG) { - "[showIncomingCall] callId: ${callId.id}, callDisplayName: $callDisplayName, notification: ${notification != null}" - } - val hasActiveCall = StreamVideo.instanceOrNull()?.state?.activeCall?.value != null - StreamLog.d(TAG) { "[showIncomingCall] hasActiveCall: $hasActiveCall" } - safeCallWithResult { - val result = if (!hasActiveCall) { - StreamLog.d(TAG) { "[showIncomingCall] Starting foreground service" } - ContextCompat.startForegroundService( - context, - buildStartIntent( - context, - callId, - TRIGGER_INCOMING_CALL, - callDisplayName, - callServiceConfiguration, - ), - ) - ComponentName(context, CallService::class.java) - } else { - StreamLog.d(TAG) { "[showIncomingCall] Starting regular service" } - context.startService( - buildStartIntent( - context, - callId, - TRIGGER_INCOMING_CALL, - callDisplayName, - callServiceConfiguration, - ), - ) - } - result!! - }.onError { - // Show notification - StreamLog.e(TAG) { "Could not start service, showing notification only: $it" } - val hasPermission = ContextCompat.checkSelfPermission( - context, - Manifest.permission.POST_NOTIFICATIONS, - ) == PackageManager.PERMISSION_GRANTED - StreamLog.i(TAG) { "Has permission: $hasPermission" } - StreamLog.i(TAG) { "Notification: $notification" } - if (hasPermission && notification != null) { - StreamLog.d(TAG) { - "[showIncomingCall] Showing notification fallback with ID: ${callId.getNotificationId( - NotificationType.Incoming, - )}" - } - StreamVideo.instanceOrNull()?.getStreamNotificationDispatcher()?.notify( - callId, - callId.getNotificationId(NotificationType.Incoming), - notification, - ) - } else { - StreamLog.w(TAG) { - "[showIncomingCall] Cannot show notification - hasPermission: $hasPermission, notification: ${notification != null}" - } - } - } - } - - @Deprecated("", level = DeprecationLevel.ERROR) - fun removeIncomingCall( - context: Context, - callId: StreamCallId, - config: CallServiceConfig = DefaultCallConfigurations.default, - ) { - safeCallWithResult { - context.startService( - buildStartIntent( - context, - callId, - TRIGGER_REMOVE_INCOMING_CALL, - "showIncomingCall, trigger:$TRIGGER_REMOVE_INCOMING_CALL", - callServiceConfiguration = config, - ), - )!! - }.onError { - NotificationManagerCompat.from( - context, - ).cancel(callId.getNotificationId(NotificationType.Incoming)) - } - } - - private fun isServiceRunning(context: Context, serviceClass: Class<*>): Boolean = - safeCallWithDefault(true) { - val activityManager = context.getSystemService( - Context.ACTIVITY_SERVICE, - ) as ActivityManager - val runningServices = activityManager.getRunningServices(Int.MAX_VALUE) - for (service in runningServices) { - if (serviceClass.name == service.service.className) { - StreamLog.w(TAG) { "Service is running: $serviceClass" } - return true - } - } - StreamLog.w(TAG) { "Service is NOT running: $serviceClass" } - return false - } } private fun shouldStopServiceFromIntent(intent: Intent?): Boolean { From c91de2a64d1d2423a9aba20ac83c233dad022a16 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 11:38:00 +0530 Subject: [PATCH 13/29] Remove comments --- .../src/main/kotlin/io/getstream/video/android/core/Call.kt | 2 +- .../kotlin/io/getstream/video/android/core/call/RtcSession.kt | 4 ++-- .../core/notifications/internal/service/CallService.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 071e60b5654..d1f382334a3 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -765,7 +765,7 @@ public class Call( private var reconnectJob: Job? = null private suspend fun schedule(key: String, block: suspend () -> Unit) { - logger.d { "[schedule] #reconnect; no args" } // noob 4 + logger.d { "[schedule] #reconnect; no args" } streamSingleFlightProcessorImpl.run(key, block) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index b27b750c43e..b10bfcce78e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -626,7 +626,7 @@ public class RtcSession internal constructor( setMuteState(isEnabled = true, TrackType.TRACK_TYPE_VIDEO) val streamId = buildTrackId(TrackType.TRACK_TYPE_VIDEO) - val track = publisher?.publishStream( // noob 9 + val track = publisher?.publishStream( streamId, TrackType.TRACK_TYPE_VIDEO, call.mediaManager.camera.resolution.value, @@ -1048,7 +1048,7 @@ public class RtcSession internal constructor( call.state.getOrCreateParticipant(it) } call.state.replaceParticipants(participantStates) - sfuConnectionModule.socketConnection.whenConnected { // noob 3 + sfuConnectionModule.socketConnection.whenConnected { logger.d { "JoinCallResponseEvent sfuConnectionModule.socketConnection.whenConnected" } if (publisher == null) { publisher = createPublisher(event.publishOptions) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index 2c93d4614fb..8cb4ee2c7c9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -486,7 +486,7 @@ internal open class CallService : Service() { call.reject( source = "RingingState.RejectedByAll", RejectReason.Decline, - ) // noob 2 + ) } callSoundPlayer?.stopCallSound() stopService() From 859add12a38e3b5ccb7b3e1299d36ce173510cf1 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 11:42:44 +0530 Subject: [PATCH 14/29] Remove dependencies --- stream-video-android-core/build.gradle.kts | 3 --- 1 file changed, 3 deletions(-) diff --git a/stream-video-android-core/build.gradle.kts b/stream-video-android-core/build.gradle.kts index 1f35b7034d6..6e812528d63 100644 --- a/stream-video-android-core/build.gradle.kts +++ b/stream-video-android-core/build.gradle.kts @@ -185,9 +185,6 @@ dependencies { implementation(libs.stream.push.delegate) api(libs.stream.push.permissions) - //permission for telecom - implementation(libs.androidx.activity) - //jetpack telecom implementation(libs.androidx.telecom) From db57fc2fbc642007597cd1f15f0757fb8d067ab4 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 11:51:59 +0530 Subject: [PATCH 15/29] fix imports --- .../io/getstream/video/android/util/StreamVideoInitHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index bb870d59987..819ebc6332d 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -31,7 +31,6 @@ import io.getstream.video.android.BuildConfig import io.getstream.video.android.app import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoBuilder -import io.getstream.video.android.core.TelecomIntegrationType import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver @@ -41,6 +40,7 @@ import io.getstream.video.android.core.notifications.handlers.CompatibilityStrea import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig +import io.getstream.video.android.core.notifications.internal.telecom.TelecomIntegrationType import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.data.services.stream.GetAuthDataResponse import io.getstream.video.android.data.services.stream.StreamService From 107bf773e059721b2c5d610492f029d831fb9802 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 12:11:59 +0530 Subject: [PATCH 16/29] fix paparazi tests --- .../internal/telecom/TelecomPermissions.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt index 6b82e562072..33c03173eb2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt @@ -90,10 +90,19 @@ class TelecomPermissions { fun supportsTelecom(context: Context): Boolean { val pm = context.packageManager val hasTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) - - val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as? TelecomManager - val hasDefaultDialer = telecomManager?.defaultDialerPackage?.isNotEmpty() == true - + val hasDefaultDialer = getSafeTelecomManager(context)?.defaultDialerPackage?.isNotEmpty() == true return hasTelephony && hasDefaultDialer } + + private fun getSafeTelecomManager(context: Context): TelecomManager? { + try { + val telecomManager = context.getSystemService( + Context.TELECOM_SERVICE, + ) as? TelecomManager + return telecomManager + } catch (e: AssertionError) { + // Paparazzi/Robolectric throws AssertionError: Unsupported Service: telecom + return null + } + } } From 1ab50bf6e2b781f51d055abce3ce6f49d4ec1a76 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 12:23:52 +0530 Subject: [PATCH 17/29] reaarange files in correct packages --- .../io/getstream/video/android/core/StreamVideoClient.kt | 2 +- .../notifications/internal/service/IncomingCallPresenter.kt | 1 - .../notifications/internal/service/ServiceIntentBuilder.kt | 2 -- .../core/notifications/internal/service/ServiceLauncher.kt | 2 -- .../TelecomServiceParam.kt => service/ServiceParam.kt} | 4 +--- 5 files changed, 2 insertions(+), 9 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/{telecom/TelecomServiceParam.kt => service/ServiceParam.kt} (81%) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index fbd3ce25ca3..97fc7076486 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -98,7 +98,7 @@ import io.getstream.video.android.core.notifications.internal.StreamNotification import io.getstream.video.android.core.notifications.internal.service.ANY_MARKER import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder -import io.getstream.video.android.core.notifications.internal.telecom.StopServiceParam +import io.getstream.video.android.core.notifications.internal.service.StopServiceParam import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt index 0df1e9c41ea..068c797eed5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenter.kt @@ -26,7 +26,6 @@ import io.getstream.log.taggedLogger import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL -import io.getstream.video.android.core.notifications.internal.telecom.StartServiceParam import io.getstream.video.android.core.utils.safeCallWithResult import io.getstream.video.android.model.StreamCallId diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt index 6c92a4d3ed0..1731575e12b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilder.kt @@ -28,8 +28,6 @@ import io.getstream.video.android.core.notifications.internal.service.CallServic import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_OUTGOING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL -import io.getstream.video.android.core.notifications.internal.telecom.StartServiceParam -import io.getstream.video.android.core.notifications.internal.telecom.StopServiceParam import io.getstream.video.android.core.utils.safeCallWithDefault import io.getstream.video.android.model.StreamCallId diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index 1cf6e2b01a7..b592a83a7b5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -51,8 +51,6 @@ import io.getstream.video.android.core.notifications.internal.VideoPushDelegate. import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction import io.getstream.video.android.core.notifications.internal.telecom.JetpackTelecomRepository -import io.getstream.video.android.core.notifications.internal.telecom.StartServiceParam -import io.getstream.video.android.core.notifications.internal.telecom.StopServiceParam import io.getstream.video.android.core.notifications.internal.telecom.TelecomCall import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallAction import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceParam.kt similarity index 81% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceParam.kt index a78a4842c16..087dc7b374b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomServiceParam.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceParam.kt @@ -14,11 +14,9 @@ * limitations under the License. */ -package io.getstream.video.android.core.notifications.internal.telecom +package io.getstream.video.android.core.notifications.internal.service import io.getstream.video.android.core.Call -import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig -import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations import io.getstream.video.android.model.StreamCallId internal data class StartServiceParam( From d718f084929619b757dd0b816a23b589c527627d Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 12:25:23 +0530 Subject: [PATCH 18/29] refactor --- .../internal/telecom/IncomingCallTelecomAction.kt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt index 49c8881c98b..b26722388ac 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt @@ -67,19 +67,4 @@ internal class IncomingCallTelecomAction( else -> {} } } - - fun onShowIncomingCallUi( - callId: StreamCallId, - callDisplayName: String?, - callServiceConfiguration: CallServiceConfig, - notification: Notification?, - ) { - incomingCallPresenter.showIncomingCall( - context, - callId, - callDisplayName, - callServiceConfiguration, - notification, - ) - } } From b70575de81eae09af92b1d77e0016caecbbe9454 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 12:29:19 +0530 Subject: [PATCH 19/29] refactor --- .../api/stream-video-android-core.api | 108 +++++++++--------- .../getstream/video/android/core/CallState.kt | 2 +- .../internal/service/ServiceLauncher.kt | 9 +- .../telecom/IncomingCallTelecomAction.kt | 10 +- .../internal/telecom/TelecomCallController.kt | 3 + .../{ => jetpack}/JetpackTelecomRepository.kt | 3 +- .../telecom/{ => jetpack}/TelecomCall.kt | 2 +- .../{ => jetpack}/TelecomCallAction.kt | 2 +- 8 files changed, 67 insertions(+), 72 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/{ => jetpack}/JetpackTelecomRepository.kt (99%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/{ => jetpack}/TelecomCall.kt (99%) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/{ => jetpack}/TelecomCallAction.kt (99%) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 3eda7bd4bfd..fc4e0419e4c 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -10424,94 +10424,94 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final fun getLivestreamGuestCallServiceConfig ()Ljava/util/Map; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate$Creator : android/os/Parcelable$Creator { +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController { + public fun (Landroid/content/Context;)V + public final fun getContext ()Landroid/content/Context; + public final fun leaveCall (Lio/getstream/video/android/core/Call;)V + public final fun onAnswer (Lio/getstream/video/android/core/Call;)V +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig { + public fun (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; + public final fun copy (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getIntegrationType ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; + public final fun getSchema ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType : java/lang/Enum { + public static final field JETPACK_TELECOM Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; + public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; +} + +public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions { public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; - public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Activate; - public synthetic fun newArray (I)[Ljava/lang/Object; + public final fun canUseTelecom (Landroid/content/Context;)Z + public final fun getRequiredPermissionsArray (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)[Ljava/lang/String; + public final fun supportsTelecom (Landroid/content/Context;)Z } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer$Creator : android/os/Parcelable$Creator { +public final class io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Activate$Creator : android/os/Parcelable$Creator { public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Activate; public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Answer; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Activate; public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect$Creator : android/os/Parcelable$Creator { +public final class io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Answer$Creator : android/os/Parcelable$Creator { public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Answer; public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Disconnect; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Answer; public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold$Creator : android/os/Parcelable$Creator { +public final class io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Disconnect$Creator : android/os/Parcelable$Creator { public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold; + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Disconnect; public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$Hold; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Disconnect; public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint$Creator : android/os/Parcelable$Creator { +public final class io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Hold$Creator : android/os/Parcelable$Creator { public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Hold; public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$SwitchAudioEndpoint; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$Hold; public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute$Creator : android/os/Parcelable$Creator { +public final class io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$SwitchAudioEndpoint$Creator : android/os/Parcelable$Creator { public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$SwitchAudioEndpoint; public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$ToggleMute; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$SwitchAudioEndpoint; public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall$Creator : android/os/Parcelable$Creator { +public final class io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$ToggleMute$Creator : android/os/Parcelable$Creator { public fun ()V - public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$ToggleMute; public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; - public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction$TransferCall; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$ToggleMute; public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController { - public fun (Landroid/content/Context;)V - public final fun getContext ()Landroid/content/Context; - public final fun leaveCall (Lio/getstream/video/android/core/Call;)V - public final fun onAnswer (Lio/getstream/video/android/core/Call;)V -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig { - public fun (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; - public final fun copy (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getIntegrationType ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; - public final fun getSchema ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType : java/lang/Enum { - public static final field JETPACK_TELECOM Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; - public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; -} - -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions { +public final class io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$TransferCall$Creator : android/os/Parcelable$Creator { public fun ()V - public final fun canUseTelecom (Landroid/content/Context;)Z - public final fun getRequiredPermissionsArray (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)[Ljava/lang/String; - public final fun supportsTelecom (Landroid/content/Context;)Z + public final fun createFromParcel (Landroid/os/Parcel;)Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$TransferCall; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lio/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction$TransferCall; + public synthetic fun newArray (I)[Ljava/lang/Object; } public final class io/getstream/video/android/core/notifications/medianotifications/MediaNotificationConfig { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index 01fa1102b94..f9b00d32406 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -107,7 +107,7 @@ import io.getstream.video.android.core.model.RejectReason import io.getstream.video.android.core.model.ScreenSharingSession import io.getstream.video.android.core.model.VisibilityOnScreenState import io.getstream.video.android.core.notifications.IncomingNotificationData -import io.getstream.video.android.core.notifications.internal.telecom.JetpackTelecomRepository +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository import io.getstream.video.android.core.permission.PermissionRequest import io.getstream.video.android.core.pinning.PinType import io.getstream.video.android.core.pinning.PinUpdateAtTime diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index b592a83a7b5..ed4ce2d077f 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -50,11 +50,11 @@ import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.core.notifications.internal.VideoPushDelegate.Companion.DEFAULT_CALL_TEXT import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction -import io.getstream.video.android.core.notifications.internal.telecom.JetpackTelecomRepository -import io.getstream.video.android.core.notifications.internal.telecom.TelecomCall -import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallAction import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCallAction import io.getstream.video.android.core.utils.safeCallWithResult import io.getstream.video.android.model.StreamCallId import kotlinx.coroutines.Dispatchers @@ -246,9 +246,8 @@ internal class ServiceLauncher(val context: Context) { } val streamVideo = StreamVideo.instance() - val incomingCallPresenter = IncomingCallPresenter(ServiceIntentBuilder()) val incomingCallTelecomAction = - IncomingCallTelecomAction(context, streamVideo, incomingCallPresenter) + IncomingCallTelecomAction(streamVideo) logger.d { "[getJetpackTelecomRepository] hashcode callsManager:${callsManager.hashCode()}" } return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt index b26722388ac..c4818d30415 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/IncomingCallTelecomAction.kt @@ -16,23 +16,15 @@ package io.getstream.video.android.core.notifications.internal.telecom -import android.app.Notification -import android.content.Context import io.getstream.video.android.core.ExternalCallRejectionHandler import io.getstream.video.android.core.ExternalCallRejectionSource import io.getstream.video.android.core.RingingState import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.notifications.IncomingNotificationAction -import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig -import io.getstream.video.android.core.notifications.internal.service.IncomingCallPresenter import io.getstream.video.android.model.StreamCallId import kotlinx.coroutines.launch -internal class IncomingCallTelecomAction( - private val context: Context, - private val streamVideo: StreamVideo, - private val incomingCallPresenter: IncomingCallPresenter, -) { +internal class IncomingCallTelecomAction(private val streamVideo: StreamVideo) { fun onAnswer(callId: StreamCallId) { val pendingIntentMap = streamVideo.call(callId.type, callId.id) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt index 61bf88e11a5..c9e333af561 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt @@ -20,6 +20,9 @@ import android.content.Context import android.telecom.DisconnectCause import io.getstream.android.video.generated.models.OwnCapability import io.getstream.video.android.core.Call +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.InteractionSource +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCallAction /** * Valid disconnected cause: [DisconnectCause.LOCAL, DisconnectCause.REMOTE, DisconnectCause.MISSED, or DisconnectCause.REJECTED] diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/JetpackTelecomRepository.kt similarity index 99% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/JetpackTelecomRepository.kt index 60cdceffe98..183bd5a8e45 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/JetpackTelecomRepository.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/JetpackTelecomRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.notifications.internal.telecom +package io.getstream.video.android.core.notifications.internal.telecom.jetpack import android.Manifest import android.net.Uri @@ -27,6 +27,7 @@ import androidx.core.telecom.CallControlResult import androidx.core.telecom.CallControlScope import androidx.core.telecom.CallsManager import io.getstream.log.taggedLogger +import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction import io.getstream.video.android.model.StreamCallId import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCall.kt similarity index 99% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCall.kt index 848f0104857..777c4ec1302 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCall.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCall.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.notifications.internal.telecom +package io.getstream.video.android.core.notifications.internal.telecom.jetpack import android.os.ParcelUuid import android.telecom.DisconnectCause diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction.kt similarity index 99% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction.kt index 1dc6ecba49f..16c0b2d53fd 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallAction.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCallAction.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.notifications.internal.telecom +package io.getstream.video.android.core.notifications.internal.telecom.jetpack import android.os.ParcelUuid import android.os.Parcelable From ed23d048d82a53d153fea38443266f70c6f5302f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 17:00:42 +0530 Subject: [PATCH 20/29] fix double activation of telecom --- .../core/notifications/internal/telecom/TelecomCallController.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt index c9e333af561..565d8165676 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt @@ -49,7 +49,6 @@ class TelecomCallController(val context: Context) { fun onAnswer(call: Call) { performAction(call) { if (it is TelecomCall.Registered) { - it.processAction(TelecomCallAction.Activate) it.processAction(TelecomCallAction.Answer(!isVideoCall(call))) } } From c0a8077a4a58b0b733d9e060a0e106fc5e81dde0 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 17:29:36 +0530 Subject: [PATCH 21/29] improve Unit tests --- .../internal/service/CallServiceTest.kt | 242 ------------------ .../service/ServiceIntentBuilderTest.kt | 166 ++++++++++++ .../ServiceNotificationRetrieverTest.kt | 142 ++++++++++ 3 files changed, 308 insertions(+), 242 deletions(-) create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt index 68357669863..2b139bda51e 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt @@ -38,9 +38,7 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull -import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -92,206 +90,6 @@ class CallServiceTest { assertEquals(ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL, callService.serviceType) } - // Test intent building - @Test - fun `buildStartIntent creates correct intent for incoming call`() { - // When - val intent = CallService.buildStartIntent( - context = context, - callId = testCallId, - trigger = TRIGGER_INCOMING_CALL, - callDisplayName = "John Doe", - ) - - // Then - assertEquals(CallService::class.java.name, intent.component?.className) - assertEquals(testCallId, intent.streamCallId(INTENT_EXTRA_CALL_CID)) - assertEquals("John Doe", intent.streamCallDisplayName(INTENT_EXTRA_CALL_DISPLAY_NAME)) - assertEquals(TRIGGER_INCOMING_CALL, intent.getStringExtra(TRIGGER_KEY)) - } - - @Test - fun `buildStartIntent creates correct intent for outgoing call`() { - // When - val intent = CallService.buildStartIntent( - context = context, - callId = testCallId, - trigger = TRIGGER_OUTGOING_CALL, - ) - - // Then - assertEquals(CallService::class.java.name, intent.component?.className) - assertEquals(testCallId, intent.streamCallId(INTENT_EXTRA_CALL_CID)) - assertEquals(TRIGGER_OUTGOING_CALL, intent.getStringExtra(TRIGGER_KEY)) - assertNull(intent.streamCallDisplayName(INTENT_EXTRA_CALL_DISPLAY_NAME)) - } - - @Test - fun `buildStartIntent creates correct intent for ongoing call`() { - // When - val intent = CallService.buildStartIntent( - context = context, - callId = testCallId, - trigger = TRIGGER_ONGOING_CALL, - ) - - // Then - assertEquals(CallService::class.java.name, intent.component?.className) - assertEquals(testCallId, intent.streamCallId(INTENT_EXTRA_CALL_CID)) - assertEquals(TRIGGER_ONGOING_CALL, intent.getStringExtra(TRIGGER_KEY)) - } - - @Test - fun `buildStartIntent creates correct intent for remove incoming call`() { - // When - val intent = CallService.buildStartIntent( - context = context, - callId = testCallId, - trigger = TRIGGER_REMOVE_INCOMING_CALL, - ) - - // Then - assertEquals(CallService::class.java.name, intent.component?.className) - assertEquals(testCallId, intent.streamCallId(INTENT_EXTRA_CALL_CID)) - assertEquals(TRIGGER_REMOVE_INCOMING_CALL, intent.getStringExtra(TRIGGER_KEY)) - } - - @Test - fun `buildStartIntent throws exception for invalid trigger`() { - // When & Then - assertThrows(IllegalArgumentException::class.java) { - CallService.buildStartIntent( - context = context, - callId = testCallId, - trigger = "invalid_trigger", - ) - } - } - - @Test - fun `buildStartIntent uses custom service class from configuration`() { - // Given - val customConfig = CallServiceConfig( - serviceClass = LivestreamCallService::class.java, - ) - - // When - val intent = CallService.buildStartIntent( - context = context, - callId = testCallId, - trigger = TRIGGER_INCOMING_CALL, - callServiceConfiguration = customConfig, - ) - - // Then - assertEquals(LivestreamCallService::class.java.name, intent.component?.className) - } - - @Test - fun `buildStopIntent creates correct intent`() { - // When - val intent = CallService.buildStopIntent(context) - - // Then - assertNotNull(intent) - assertEquals(CallService::class.java.name, intent.component?.className) - } - - @Test - fun `buildStopIntent uses custom service class from configuration`() { - // Given - val customConfig = CallServiceConfig( - serviceClass = LivestreamCallService::class.java, - ) - - // When - val intent = CallService.buildStopIntent( - context = context, - callServiceConfiguration = customConfig, - ) - - // Then - assertNotNull(intent) - // Note: The actual implementation has some complex logic for running services - // so we just verify the intent is created - } - - // Test notification generation logic - @Test - fun `getNotificationPair returns correct data for ongoing call`() { - // Given - every { - mockStreamVideoClient.getOngoingCallNotification(any(), any(), payload = any()) - } returns mockNotification - - // When - val result = callService.getNotificationPair( - trigger = TRIGGER_ONGOING_CALL, - streamVideo = mockStreamVideoClient, - streamCallId = testCallId, - intentCallDisplayName = "John Doe", - ) - - // Then - assertEquals(mockNotification, result.first) - assertEquals(testCallId.hashCode(), result.second) - } - - @Test - fun `getNotificationPair returns correct data for incoming call`() { - // Given - val mockState = mockk() - every { mockStreamVideoClient.state } returns mockState - every { mockState.activeCall } returns mockk { - every { value } returns null - } - every { - mockStreamVideoClient.getRingingCallNotification(any(), any(), any(), any(), any()) - } returns mockNotification - - // When - val result = callService.getNotificationPair( - trigger = TRIGGER_INCOMING_CALL, - streamVideo = mockStreamVideoClient, - streamCallId = testCallId, - intentCallDisplayName = "John Doe", - ) - - // Then - assertEquals(mockNotification, result.first) - assertEquals(testCallId.getNotificationId(NotificationType.Incoming), result.second) - } - - @Test - fun `getNotificationPair returns null notification for remove incoming call`() { - // When - val result = callService.getNotificationPair( - trigger = TRIGGER_REMOVE_INCOMING_CALL, - streamVideo = mockStreamVideoClient, - streamCallId = testCallId, - intentCallDisplayName = null, - ) - - // Then - assertNull(result.first) - assertEquals(testCallId.getNotificationId(NotificationType.Incoming), result.second) - } - - @Test - fun `getNotificationPair returns null notification for unknown trigger`() { - // When - val result = callService.getNotificationPair( - trigger = "unknown_trigger", - streamVideo = mockStreamVideoClient, - streamCallId = testCallId, - intentCallDisplayName = null, - ) - - // Then - assertNull(result.first) - assertEquals(testCallId.hashCode(), result.second) - } - // Test service subclasses have correct service types @Test fun `LivestreamCallService has correct service type`() { @@ -350,46 +148,6 @@ class CallServiceTest { assertNull(intent.streamCallDisplayName(INTENT_EXTRA_CALL_DISPLAY_NAME)) } - // Test service configuration integration - @Test - fun `service respects configuration for different call types`() { - // Given - val livestreamConfig = CallServiceConfig( - serviceClass = LivestreamCallService::class.java, - runCallServiceInForeground = true, - ) - - // When - val intent = CallService.buildStartIntent( - context = context, - callId = StreamCallId(type = "livestream", id = "test-123"), - trigger = TRIGGER_INCOMING_CALL, - callServiceConfiguration = livestreamConfig, - ) - - // Then - assertEquals(LivestreamCallService::class.java.name, intent.component?.className) - } - - // Test error handling - @Test - fun `service handles missing StreamVideo instance gracefully in notification generation`() { - // Given - Using a real CallService instance but with mocked dependencies - val service = CallService() - - // When - Call getNotificationPair with minimal valid parameters - val result = service.getNotificationPair( - trigger = TRIGGER_REMOVE_INCOMING_CALL, // This trigger doesn't need StreamVideo methods - streamVideo = mockStreamVideoClient, - streamCallId = testCallId, - intentCallDisplayName = null, - ) - - // Then - Should handle gracefully and return expected result - assertNull(result.first) // No notification for remove trigger - assertEquals(testCallId.getNotificationId(NotificationType.Incoming), result.second) - } - // Test constants consistency @Test fun `notification ID constants are consistent`() { diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt new file mode 100644 index 00000000000..61529c49911 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt @@ -0,0 +1,166 @@ +package io.getstream.video.android.core.notifications.internal.service + +import android.content.Context +import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_CID +import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_DISPLAY_NAME +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_KEY +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_OUTGOING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL +import io.getstream.video.android.model.StreamCallId +import io.getstream.video.android.model.streamCallDisplayName +import io.getstream.video.android.model.streamCallId +import io.mockk.MockKAnnotations +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class ServiceIntentBuilderTest { + + private lateinit var context: Context + private lateinit var callService: CallService + private lateinit var testCallId: StreamCallId + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + context = RuntimeEnvironment.getApplication() + callService = CallService() + testCallId = StreamCallId(type = "default", id = "test-call-123") + } + + + @Test + fun `buildStartIntent creates correct intent for outgoing call`() { + // When + + val intent = ServiceIntentBuilder().buildStartIntent( + context, + StartServiceParam( + callId = testCallId, + trigger = TRIGGER_OUTGOING_CALL, + ) + ) + + // Then + assertEquals(CallService::class.java.name, intent.component?.className) + assertEquals(testCallId, intent.streamCallId(INTENT_EXTRA_CALL_CID)) + assertEquals(TRIGGER_OUTGOING_CALL, intent.getStringExtra(TRIGGER_KEY)) + assertNull(intent.streamCallDisplayName(INTENT_EXTRA_CALL_DISPLAY_NAME)) + } + + @Test + fun `buildStartIntent creates correct intent for ongoing call`() { + // When + val intent = ServiceIntentBuilder().buildStartIntent( + context, + StartServiceParam( + callId = testCallId, + trigger = TRIGGER_ONGOING_CALL, + ) + ) + + // Then + assertEquals(CallService::class.java.name, intent.component?.className) + assertEquals(testCallId, intent.streamCallId(INTENT_EXTRA_CALL_CID)) + assertEquals(TRIGGER_ONGOING_CALL, intent.getStringExtra(TRIGGER_KEY)) + } + + @Test + fun `buildStartIntent creates correct intent for remove incoming call`() { + // When + val intent = ServiceIntentBuilder().buildStartIntent( + context = context, + StartServiceParam(testCallId, TRIGGER_REMOVE_INCOMING_CALL) + ) + + // Then + assertEquals(CallService::class.java.name, intent.component?.className) + assertEquals(testCallId, intent.streamCallId(INTENT_EXTRA_CALL_CID)) + assertEquals(TRIGGER_REMOVE_INCOMING_CALL, intent.getStringExtra(TRIGGER_KEY)) + } + + @Test + fun `buildStartIntent throws exception for invalid trigger`() { + // When & Then + assertThrows(IllegalArgumentException::class.java) { + ServiceIntentBuilder().buildStartIntent( + context, StartServiceParam(testCallId, "invalid_trigger") + ) + } + } + + @Test + fun `buildStartIntent uses custom service class from configuration`() { + // Given + val customConfig = CallServiceConfig( + serviceClass = LivestreamCallService::class.java, + ) + + // When + val intent = ServiceIntentBuilder().buildStartIntent(context,StartServiceParam( + callId = testCallId, + trigger = TRIGGER_INCOMING_CALL, + callServiceConfiguration = customConfig, + )) + + // Then + assertEquals(LivestreamCallService::class.java.name, intent.component?.className) + } + + @Test + fun `buildStopIntent creates correct intent`() { + // When + val intent = ServiceIntentBuilder().buildStopIntent(context, StopServiceParam()) + + // Then + assertNotNull(intent) + assertEquals(CallService::class.java.name, intent.component?.className) + } +// + @Test + fun `buildStopIntent uses custom service class from configuration`() { + // Given + val customConfig = CallServiceConfig( + serviceClass = LivestreamCallService::class.java, + ) + + // When + val intent = ServiceIntentBuilder().buildStopIntent( + context, + StopServiceParam(callServiceConfiguration = customConfig), + ) + + // Then + assertNotNull(intent) + // Note: The actual implementation has some complex logic for running services + // so we just verify the intent is created + } + + @Test + fun `service respects configuration for different call types`() { + // Given + val livestreamConfig = CallServiceConfig( + serviceClass = LivestreamCallService::class.java, + runCallServiceInForeground = true, + ) + + // When + val intent = ServiceIntentBuilder().buildStartIntent(context, StartServiceParam( + callId = StreamCallId(type = "livestream", id = "test-123"), + trigger = TRIGGER_INCOMING_CALL, + callServiceConfiguration = livestreamConfig, + )) + + // Then + assertEquals(LivestreamCallService::class.java.name, intent.component?.className) + } +} \ No newline at end of file diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt new file mode 100644 index 00000000000..b90ce182bda --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt @@ -0,0 +1,142 @@ +package io.getstream.video.android.core.notifications.internal.service + +import android.app.Notification +import android.content.Context +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.NotificationType +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL +import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL +import io.getstream.video.android.model.StreamCallId +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class ServiceNotificationRetrieverTest { + + @MockK + private lateinit var mockStreamVideoClient: StreamVideoClient + + @MockK + lateinit var mockNotification: Notification + + private lateinit var context: Context + private lateinit var serviceNotificationRetriever: ServiceNotificationRetriever + private lateinit var testCallId: StreamCallId + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + context = RuntimeEnvironment.getApplication() +// callService = CallService() + serviceNotificationRetriever = ServiceNotificationRetriever() + testCallId = StreamCallId(type = "default", id = "test-call-123") + } + + // Test notification generation logic + @Test + fun `getNotificationPair returns correct data for ongoing call`() { + // Given + every { + mockStreamVideoClient.getOngoingCallNotification(any(), any(), payload = any()) + } returns mockNotification + + // When + val result = serviceNotificationRetriever.getNotificationPair( + context = context, + trigger = TRIGGER_ONGOING_CALL, + streamVideo = mockStreamVideoClient, + streamCallId = testCallId, + intentCallDisplayName = "John Doe", + ) + + // Then + assertEquals(mockNotification, result.first) + assertEquals(testCallId.hashCode(), result.second) + } + + @Test + fun `getNotificationPair returns correct data for incoming call`() { + // Given + val mockState = mockk() + every { mockStreamVideoClient.state } returns mockState + every { mockState.activeCall } returns mockk { + every { value } returns null + } + every { + mockStreamVideoClient.getRingingCallNotification(any(), any(), any(), any(), any()) + } returns mockNotification + + // When + val result = serviceNotificationRetriever.getNotificationPair( + context = context, + trigger = TRIGGER_INCOMING_CALL, + streamVideo = mockStreamVideoClient, + streamCallId = testCallId, + intentCallDisplayName = "John Doe", + ) + + // Then + assertEquals(mockNotification, result.first) + assertEquals(testCallId.getNotificationId(NotificationType.Incoming), result.second) + } + + @Test + fun `getNotificationPair returns null notification for remove incoming call`() { + // When + val result = serviceNotificationRetriever.getNotificationPair( + context = context, + trigger = TRIGGER_REMOVE_INCOMING_CALL, + streamVideo = mockStreamVideoClient, + streamCallId = testCallId, + intentCallDisplayName = null, + ) + + // Then + assertNull(result.first) + assertEquals(testCallId.getNotificationId(NotificationType.Incoming), result.second) + } + + @Test + fun `getNotificationPair returns null notification for unknown trigger`() { + // When + val result = serviceNotificationRetriever.getNotificationPair( + context = context, + trigger = "unknown_trigger", + streamVideo = mockStreamVideoClient, + streamCallId = testCallId, + intentCallDisplayName = null, + ) + + // Then + assertNull(result.first) + assertEquals(testCallId.hashCode(), result.second) + } + + @Test + fun `service handles missing StreamVideo instance gracefully in notification generation`() { + // Given - Using a real CallService instance but with mocked dependencies + + // When - Call getNotificationPair with minimal valid parameters + val result = serviceNotificationRetriever.getNotificationPair( + context, + trigger = TRIGGER_REMOVE_INCOMING_CALL, // This trigger doesn't need StreamVideo methods + streamVideo = mockStreamVideoClient, + streamCallId = testCallId, + intentCallDisplayName = null, + ) + + // Then - Should handle gracefully and return expected result + assertNull(result.first) // No notification for remove trigger + assertEquals(testCallId.getNotificationId(NotificationType.Incoming), result.second) + } +} \ No newline at end of file From 97f885853a174a4804553acab89c185ffdd9880f Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 17:36:24 +0530 Subject: [PATCH 22/29] refactor api --- .../android/util/StreamVideoInitHelper.kt | 2 - .../api/stream-video-android-core.api | 8 +-- .../video/android/core/ClientState.kt | 6 +- .../internal/telecom/TelecomConfig.kt | 2 +- .../internal/service/CallServiceTest.kt | 3 - .../service/ServiceIntentBuilderTest.kt | 69 ++++++++++++------- .../ServiceNotificationRetrieverTest.kt | 18 ++++- 7 files changed, 72 insertions(+), 36 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 819ebc6332d..fca5441eeec 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -40,7 +40,6 @@ import io.getstream.video.android.core.notifications.handlers.CompatibilityStrea import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations import io.getstream.video.android.core.notifications.internal.telecom.TelecomConfig -import io.getstream.video.android.core.notifications.internal.telecom.TelecomIntegrationType import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.data.services.stream.GetAuthDataResponse import io.getstream.video.android.data.services.stream.StreamService @@ -295,7 +294,6 @@ object StreamVideoInitHelper { audioProcessing = NoiseCancellation(context), telecomConfig = TelecomConfig( context.packageName, - TelecomIntegrationType.JETPACK_TELECOM, ), ).build() } diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index fc4e0419e4c..e7a3339c721 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -10432,13 +10432,11 @@ public final class io/getstream/video/android/core/notifications/internal/teleco } public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig { - public fun (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)V + public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; - public final fun copy (Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Ljava/lang/String;Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public final fun copy (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig; public fun equals (Ljava/lang/Object;)Z - public final fun getIntegrationType ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; public final fun getSchema ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index d2776edeedc..db64b4f0f72 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -97,9 +97,13 @@ class ClientState(private val client: StreamVideo) { activeOrRingingCall } + /** + * Hardcoded to [TelecomIntegrationType.JETPACK_TELECOM] for now. + * May switch to another later if needed. + */ fun getTelecomIntegrationType(): TelecomIntegrationType? { return if (streamVideoClient.telecomConfig != null) { - streamVideoClient.telecomConfig.integrationType + TelecomIntegrationType.JETPACK_TELECOM } else { null } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt index ac69b9d28b8..466d8f3fa62 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt @@ -16,7 +16,7 @@ package io.getstream.video.android.core.notifications.internal.telecom -data class TelecomConfig(val schema: String, val integrationType: TelecomIntegrationType) +data class TelecomConfig(val schema: String) enum class TelecomIntegrationType { JETPACK_TELECOM, diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt index 2b139bda51e..c3d023dca45 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt @@ -24,7 +24,6 @@ import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_CID import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_DISPLAY_NAME -import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_KEY import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL @@ -34,9 +33,7 @@ import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.streamCallDisplayName import io.getstream.video.android.model.streamCallId import io.mockk.MockKAnnotations -import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.mockk import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt index 61529c49911..01633a882ed 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceIntentBuilderTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.getstream.video.android.core.notifications.internal.service import android.content.Context @@ -37,7 +53,6 @@ class ServiceIntentBuilderTest { testCallId = StreamCallId(type = "default", id = "test-call-123") } - @Test fun `buildStartIntent creates correct intent for outgoing call`() { // When @@ -47,7 +62,7 @@ class ServiceIntentBuilderTest { StartServiceParam( callId = testCallId, trigger = TRIGGER_OUTGOING_CALL, - ) + ), ) // Then @@ -65,7 +80,7 @@ class ServiceIntentBuilderTest { StartServiceParam( callId = testCallId, trigger = TRIGGER_ONGOING_CALL, - ) + ), ) // Then @@ -79,7 +94,7 @@ class ServiceIntentBuilderTest { // When val intent = ServiceIntentBuilder().buildStartIntent( context = context, - StartServiceParam(testCallId, TRIGGER_REMOVE_INCOMING_CALL) + StartServiceParam(testCallId, TRIGGER_REMOVE_INCOMING_CALL), ) // Then @@ -91,10 +106,11 @@ class ServiceIntentBuilderTest { @Test fun `buildStartIntent throws exception for invalid trigger`() { // When & Then - assertThrows(IllegalArgumentException::class.java) { - ServiceIntentBuilder().buildStartIntent( - context, StartServiceParam(testCallId, "invalid_trigger") - ) + assertThrows(IllegalArgumentException::class.java) { + ServiceIntentBuilder().buildStartIntent( + context, + StartServiceParam(testCallId, "invalid_trigger"), + ) } } @@ -106,11 +122,14 @@ class ServiceIntentBuilderTest { ) // When - val intent = ServiceIntentBuilder().buildStartIntent(context,StartServiceParam( - callId = testCallId, - trigger = TRIGGER_INCOMING_CALL, - callServiceConfiguration = customConfig, - )) + val intent = ServiceIntentBuilder().buildStartIntent( + context, + StartServiceParam( + callId = testCallId, + trigger = TRIGGER_INCOMING_CALL, + callServiceConfiguration = customConfig, + ), + ) // Then assertEquals(LivestreamCallService::class.java.name, intent.component?.className) @@ -125,6 +144,7 @@ class ServiceIntentBuilderTest { assertNotNull(intent) assertEquals(CallService::class.java.name, intent.component?.className) } + // @Test fun `buildStopIntent uses custom service class from configuration`() { @@ -134,10 +154,10 @@ class ServiceIntentBuilderTest { ) // When - val intent = ServiceIntentBuilder().buildStopIntent( - context, - StopServiceParam(callServiceConfiguration = customConfig), - ) + val intent = ServiceIntentBuilder().buildStopIntent( + context, + StopServiceParam(callServiceConfiguration = customConfig), + ) // Then assertNotNull(intent) @@ -154,13 +174,16 @@ class ServiceIntentBuilderTest { ) // When - val intent = ServiceIntentBuilder().buildStartIntent(context, StartServiceParam( - callId = StreamCallId(type = "livestream", id = "test-123"), - trigger = TRIGGER_INCOMING_CALL, - callServiceConfiguration = livestreamConfig, - )) + val intent = ServiceIntentBuilder().buildStartIntent( + context, + StartServiceParam( + callId = StreamCallId(type = "livestream", id = "test-123"), + trigger = TRIGGER_INCOMING_CALL, + callServiceConfiguration = livestreamConfig, + ), + ) // Then assertEquals(LivestreamCallService::class.java.name, intent.component?.className) } -} \ No newline at end of file +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt index b90ce182bda..9ff3c3957ff 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceNotificationRetrieverTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.getstream.video.android.core.notifications.internal.service import android.app.Notification @@ -139,4 +155,4 @@ class ServiceNotificationRetrieverTest { assertNull(result.first) // No notification for remove trigger assertEquals(testCallId.getNotificationId(NotificationType.Incoming), result.second) } -} \ No newline at end of file +} From 6c9cda6ccceb9e772403c6b513f40496b17ffe84 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 17:42:16 +0530 Subject: [PATCH 23/29] refactor api --- .../api/stream-video-android-core.api | 7 ------- .../core/notifications/internal/telecom/TelecomConfig.kt | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index e7a3339c721..b9f4d35c250 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -10442,13 +10442,6 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType : java/lang/Enum { - public static final field JETPACK_TELECOM Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; - public static fun values ()[Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType; -} - public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions { public fun ()V public final fun canUseTelecom (Landroid/content/Context;)Z diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt index 466d8f3fa62..faad3a77244 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomConfig.kt @@ -16,8 +16,11 @@ package io.getstream.video.android.core.notifications.internal.telecom +import io.getstream.video.android.core.internal.InternalStreamVideoApi + data class TelecomConfig(val schema: String) +@InternalStreamVideoApi enum class TelecomIntegrationType { JETPACK_TELECOM, } From 64add4d44f1d4fb18eaacf59b512dada1104feee Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 18:43:12 +0530 Subject: [PATCH 24/29] telecom will be enabled from callServiceConfigRegistry --- .../video/android/util/StreamVideoInitHelper.kt | 9 ++++++--- .../api/stream-video-android-core.api | 13 ++++++++----- .../video/android/core/StreamVideoBuilder.kt | 1 + .../internal/service/CallServiceConfig.kt | 1 + .../internal/service/CallServiceConfigBuilder.kt | 6 ++++++ .../internal/service/CallServiceConfigRegistry.kt | 1 + .../internal/service/ServiceLauncher.kt | 4 ++-- .../internal/telecom/TelecomCallController.kt | 4 +++- .../internal/telecom/TelecomPermissions.kt | 5 +++-- 9 files changed, 31 insertions(+), 13 deletions(-) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index fca5441eeec..9e05ba2320d 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -31,6 +31,7 @@ import io.getstream.video.android.BuildConfig import io.getstream.video.android.app import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoBuilder +import io.getstream.video.android.core.call.CallType import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver @@ -206,9 +207,11 @@ object StreamVideoInitHelper { loggingLevel: LoggingLevel, ): StreamVideo { val callServiceConfigRegistry = CallServiceConfigRegistry() - callServiceConfigRegistry.register( - DefaultCallConfigurations.getLivestreamGuestCallServiceConfig(), - ) + callServiceConfigRegistry.apply { + register(DefaultCallConfigurations.getLivestreamGuestCallServiceConfig()) + register(CallType.AudioCall.name) { enableTelecom(true) } + } + return StreamVideoBuilder( context = context, apiKey = apiKey, diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index b9f4d35c250..ceccb94b5af 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -10370,17 +10370,19 @@ public final class io/getstream/video/android/core/notifications/internal/receiv public final class io/getstream/video/android/core/notifications/internal/service/CallServiceConfig { public fun ()V - public fun (ZILjava/util/Map;Ljava/lang/Class;)V - public synthetic fun (ZILjava/util/Map;Ljava/lang/Class;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZILjava/util/Map;Ljava/lang/Class;Z)V + public synthetic fun (ZILjava/util/Map;Ljava/lang/Class;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z public final fun component2 ()I public final fun component3 ()Ljava/util/Map; public final fun component4 ()Ljava/lang/Class; - public final fun copy (ZILjava/util/Map;Ljava/lang/Class;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; - public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZILjava/util/Map;Ljava/lang/Class;ILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun component5 ()Z + public final fun copy (ZILjava/util/Map;Ljava/lang/Class;Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;ZILjava/util/Map;Ljava/lang/Class;ZILjava/lang/Object;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; public fun equals (Ljava/lang/Object;)Z public final fun getAudioUsage ()I public final fun getCallServicePerType ()Ljava/util/Map; + public final fun getEnableTelecom ()Z public final fun getRunCallServiceInForeground ()Z public final fun getServiceClass ()Ljava/lang/Class; public fun hashCode ()I @@ -10390,6 +10392,7 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final class io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder { public fun ()V public final fun build ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public final fun enableTelecom (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setAudioUsage (I)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setRunCallServiceInForeground (Z)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; public final fun setServiceClass (Ljava/lang/Class;)Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder; @@ -10444,7 +10447,7 @@ public final class io/getstream/video/android/core/notifications/internal/teleco public final class io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions { public fun ()V - public final fun canUseTelecom (Landroid/content/Context;)Z + public final fun canUseTelecom (Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Landroid/content/Context;)Z public final fun getRequiredPermissionsArray (Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;)[Ljava/lang/String; public final fun supportsTelecom (Landroid/content/Context;)Z } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index 413ab7aaa15..cfefb0990b7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -334,6 +334,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( legacyCallConfig.runCallServiceInForeground, ) setAudioUsage(legacyCallConfig.audioUsage) + enableTelecom(legacyCallConfig.enableTelecom) } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt index 20e858e85b7..c7b21f4ce80 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt @@ -46,6 +46,7 @@ public data class CallServiceConfig( Pair(ANY_MARKER, CallService::class.java), ), val serviceClass: Class<*> = CallService::class.java, + val enableTelecom: Boolean = false, ) /** diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt index 72fc10645bc..b76b7cc4bf9 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigBuilder.kt @@ -22,6 +22,7 @@ class CallServiceConfigBuilder { private var serviceClass: Class<*> = CallService::class.java private var runCallServiceInForeground: Boolean = true private var audioUsage: Int = AudioAttributes.USAGE_VOICE_COMMUNICATION + private var enableTelecom: Boolean = false fun setServiceClass(serviceClass: Class<*>): CallServiceConfigBuilder = apply { this.serviceClass = serviceClass @@ -35,11 +36,16 @@ class CallServiceConfigBuilder { this.audioUsage = audioUsage } + fun enableTelecom(enableTelecom: Boolean): CallServiceConfigBuilder = apply { + this.enableTelecom = enableTelecom + } + fun build(): CallServiceConfig { return CallServiceConfig( serviceClass = serviceClass, runCallServiceInForeground = runCallServiceInForeground, audioUsage = audioUsage, + enableTelecom = enableTelecom, ) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry.kt index f37848cba24..a8ec4407d19 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry.kt @@ -160,6 +160,7 @@ class CallServiceConfigRegistry { setServiceClass(existingConfig.serviceClass) setRunCallServiceInForeground(existingConfig.runCallServiceInForeground) setAudioUsage(existingConfig.audioUsage) + enableTelecom(existingConfig.enableTelecom) updater() } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index ed4ce2d077f..adb54bedf1c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -88,7 +88,7 @@ internal class ServiceLauncher(val context: Context) { notification, ) logger.d { "[showIncomingCall] service start result: $result" } - if (telecomPermissions.canUseTelecom(context)) { + if (telecomPermissions.canUseTelecom(callServiceConfiguration, context)) { if (telecomHelper.canUseJetpackTelecom()) { when (result) { ShowIncomingCallResult.FG_SERVICE -> { @@ -163,7 +163,7 @@ internal class ServiceLauncher(val context: Context) { val telecomPermissions = TelecomPermissions() val telecomHelper = TelecomHelper() - if (telecomPermissions.canUseTelecom(context)) { + if (telecomPermissions.canUseTelecom(callConfig, context)) { if (telecomHelper.canUseJetpackTelecom()) { val jetpackTelecomRepository = getJetpackTelecomRepository(callId) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt index 565d8165676..7b85bd2ab16 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt @@ -20,6 +20,7 @@ import android.content.Context import android.telecom.DisconnectCause import io.getstream.android.video.generated.models.OwnCapability import io.getstream.video.android.core.Call +import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.notifications.internal.telecom.jetpack.InteractionSource import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCallAction @@ -59,7 +60,8 @@ class TelecomCallController(val context: Context) { } private fun performAction(call: Call, block: (TelecomCall) -> Unit) { - if (telecomPermissions.canUseTelecom(context)) { + val callConfig = (call.client as StreamVideoClient).callServiceConfigRegistry.get(call.type) + if (telecomPermissions.canUseTelecom(callConfig, context)) { if (telecomHelper.canUseJetpackTelecom()) { val telecomCall = call.state.jetpackTelecomRepository?.currentCall?.value diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt index 33c03173eb2..ea621612f44 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt @@ -25,6 +25,7 @@ import io.getstream.log.TaggedLogger import io.getstream.log.taggedLogger import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig class TelecomPermissions { @@ -83,8 +84,8 @@ class TelecomPermissions { private fun optedForTelecom() = (StreamVideo.instanceOrNull() as? StreamVideoClient)?.telecomConfig != null - fun canUseTelecom(context: Context): Boolean { - return optedForTelecom() && supportsTelecom(context) && hasPermissions(context) + fun canUseTelecom(callServiceConfig: CallServiceConfig, context: Context): Boolean { + return callServiceConfig.enableTelecom && optedForTelecom() && supportsTelecom(context) && hasPermissions(context) } fun supportsTelecom(context: Context): Boolean { From 8093f5cb317ea75735870cf7de0e92f77f1d8b40 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 7 Oct 2025 19:16:51 +0530 Subject: [PATCH 25/29] refactor --- .../internal/telecom/TelecomPermissions.kt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt index ea621612f44..bc8b5f86dca 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt @@ -38,12 +38,6 @@ class TelecomPermissions { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { with(permissions) { add(android.Manifest.permission.MANAGE_OWN_CALLS) - if (!telecomHelper.canUseJetpackTelecom()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - add(android.Manifest.permission.READ_PHONE_NUMBERS) - } - add(android.Manifest.permission.CALL_PHONE) - } } } return permissions @@ -55,14 +49,6 @@ class TelecomPermissions { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { with(permissions) { add(android.Manifest.permission.MANAGE_OWN_CALLS) - if (telecomIntegrationType == TelecomIntegrationType.JETPACK_TELECOM) { - // Do nothing - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - add(android.Manifest.permission.READ_PHONE_NUMBERS) - } - add(android.Manifest.permission.CALL_PHONE) - } } } return permissions From 83a083a7e85e20e7d8b06bb072faac2ac22aada3 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 8 Oct 2025 01:42:13 +0530 Subject: [PATCH 26/29] improve unit tests --- .../JetpackTelecomRepositoryProvider.kt | 44 ++++ .../internal/service/ServiceLauncher.kt | 26 +- .../internal/telecom/TelecomPermissions.kt | 1 - .../RejectCallBroadcastReceiverTest.kt | 89 +++++++ .../service/IncomingCallPresenterTest.kt | 196 +++++++++++++++ .../internal/service/ServiceLauncherTest.kt | 238 ++++++++++++++++++ .../telecom/TelecomCallControllerTest.kt | 161 ++++++++++++ .../telecom/TelecomPermissionsTest.kt | 186 ++++++++++++++ 8 files changed, 917 insertions(+), 24 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/JetpackTelecomRepositoryProvider.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiverTest.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenterTest.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncherTest.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallControllerTest.kt create mode 100644 stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissionsTest.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/JetpackTelecomRepositoryProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/JetpackTelecomRepositoryProvider.kt new file mode 100644 index 00000000000..d1a5b6089a4 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/JetpackTelecomRepositoryProvider.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.service + +import android.content.Context +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.telecom.CallsManager +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository +import io.getstream.video.android.model.StreamCallId + +internal class JetpackTelecomRepositoryProvider(private val context: Context) { + + @RequiresApi(Build.VERSION_CODES.O) + fun get(callId: StreamCallId): JetpackTelecomRepository { + val callsManager = CallsManager(context).apply { + registerAppWithTelecom( + capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING and + CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING, + ) + } + + val streamVideo = StreamVideo.instance() + val incomingCallTelecomAction = + IncomingCallTelecomAction(streamVideo) + return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt index adb54bedf1c..460aef60b1e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt @@ -35,13 +35,10 @@ package io.getstream.video.android.core.notifications.internal.service import android.annotation.SuppressLint import android.app.Notification import android.content.Context -import android.os.Build import android.os.Bundle -import androidx.annotation.RequiresApi import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.core.net.toUri -import androidx.core.telecom.CallsManager import io.getstream.log.taggedLogger import io.getstream.video.android.core.Call import io.getstream.video.android.core.StreamVideo @@ -49,10 +46,8 @@ import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.core.notifications.internal.VideoPushDelegate.Companion.DEFAULT_CALL_TEXT import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL -import io.getstream.video.android.core.notifications.internal.telecom.IncomingCallTelecomAction import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions -import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCallAction import io.getstream.video.android.core.utils.safeCallWithResult @@ -68,6 +63,7 @@ internal class ServiceLauncher(val context: Context) { private val incomingCallPresenter = IncomingCallPresenter(serviceIntentBuilder) private val telecomHelper = TelecomHelper() private val telecomPermissions = TelecomPermissions() + private val jetpackTelecomRepositoryProvider = JetpackTelecomRepositoryProvider(context) @SuppressLint("MissingPermission", "NewApi") fun showIncomingCall( @@ -94,7 +90,7 @@ internal class ServiceLauncher(val context: Context) { ShowIncomingCallResult.FG_SERVICE -> { updateIncomingCallNotification(notification, streamVideo, callId) - val jetpackTelecomRepository = getJetpackTelecomRepository(callId) + val jetpackTelecomRepository = jetpackTelecomRepositoryProvider.get(callId) val appSchema = (streamVideo as StreamVideoClient).telecomConfig?.schema val addressUri = "$appSchema:${callId.id}".toUri() @@ -165,7 +161,7 @@ internal class ServiceLauncher(val context: Context) { val telecomHelper = TelecomHelper() if (telecomPermissions.canUseTelecom(callConfig, context)) { if (telecomHelper.canUseJetpackTelecom()) { - val jetpackTelecomRepository = getJetpackTelecomRepository(callId) + val jetpackTelecomRepository = jetpackTelecomRepositoryProvider.get(callId) val appSchema = streamVideo.telecomConfig?.schema val addressUri = "$appSchema:${callId.id}".toUri() @@ -236,22 +232,6 @@ internal class ServiceLauncher(val context: Context) { stopCallServiceInternal(call) } - @RequiresApi(Build.VERSION_CODES.O) - private fun getJetpackTelecomRepository(callId: StreamCallId): JetpackTelecomRepository { - val callsManager = CallsManager(context).apply { - registerAppWithTelecom( - capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING and - CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING, - ) - } - - val streamVideo = StreamVideo.instance() - val incomingCallTelecomAction = - IncomingCallTelecomAction(streamVideo) - logger.d { "[getJetpackTelecomRepository] hashcode callsManager:${callsManager.hashCode()}" } - return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction) - } - private fun stopCallServiceInternal(call: Call) { val streamVideo = StreamVideo.instanceOrNull() as? StreamVideoClient streamVideo?.let { streamVideoClient -> diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt index bc8b5f86dca..563d6d79304 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissions.kt @@ -30,7 +30,6 @@ import io.getstream.video.android.core.notifications.internal.service.CallServic class TelecomPermissions { private val logger: TaggedLogger by taggedLogger("TelecomPermissions") - private val telecomHelper = TelecomHelper() private fun getRequiredPermissionsList(): List { val permissions = mutableListOf() diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiverTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiverTest.kt new file mode 100644 index 00000000000..c390e8ae315 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/receivers/RejectCallBroadcastReceiverTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.receivers + +import android.content.Context +import android.content.Intent +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.ExternalCallRejectionHandler +import io.getstream.video.android.core.ExternalCallRejectionSource +import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_REJECT_CALL +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.unmockkAll +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class RejectCallBroadcastReceiverTest { + + private lateinit var context: Context + private lateinit var intent: Intent + private lateinit var call: Call + private lateinit var rejectionHandler: ExternalCallRejectionHandler + private lateinit var receiver: RejectCallBroadcastReceiver + + @Before + fun setup() { + mockkConstructor(ExternalCallRejectionHandler::class) + + context = mockk(relaxed = true) + intent = mockk(relaxed = true) + call = mockk(relaxed = true) + rejectionHandler = mockk(relaxed = true) + + // When a new ExternalCallRejectionHandler() is created, return our mock + coEvery { + anyConstructed().onRejectCall(any(), any(), any(), any()) + } just Runs + + receiver = RejectCallBroadcastReceiver() + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `action constant should be ACTION_REJECT_CALL`() { + assertEquals(ACTION_REJECT_CALL, receiver.action) + } + + @Test + fun `onReceive should call ExternalCallRejectionHandler with NOTIFICATION source`() = runTest { + receiver.onReceive(call, context, intent) + + coVerify { + anyConstructed().onRejectCall( + ExternalCallRejectionSource.NOTIFICATION, + call, + context, + intent, + ) + } + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenterTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenterTest.kt new file mode 100644 index 00000000000..d031cd6acbb --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/IncomingCallPresenterTest.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.service + +import android.Manifest +import android.app.Notification +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.ContextCompat +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.NotificationType +import io.getstream.video.android.core.notifications.dispatchers.NotificationDispatcher +import io.getstream.video.android.model.StreamCallId +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.test.Test + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.TIRAMISU]) +class IncomingCallPresenterTest { + + private lateinit var context: Context + private lateinit var serviceIntentBuilder: ServiceIntentBuilder + private lateinit var presenter: IncomingCallPresenter + private lateinit var callServiceConfig: CallServiceConfig + private lateinit var callId: StreamCallId + private lateinit var notification: Notification + private lateinit var streamVideoClient: StreamVideoClient + + @Before + fun setup() { + context = mockk(relaxed = true) + serviceIntentBuilder = mockk(relaxed = true) + callServiceConfig = CallServiceConfig(enableTelecom = true) + callId = StreamCallId("default", "123") + notification = mockk(relaxed = true) + streamVideoClient = mockk(relaxed = true) + + mockkObject(StreamVideo) + mockkStatic(ContextCompat::class) + + every { StreamVideo.instanceOrNull() } returns streamVideoClient + every { StreamVideo.instance() } returns streamVideoClient + + presenter = IncomingCallPresenter(serviceIntentBuilder) + } + + @After + fun tearDown() { + unmockkAll() + } + + // region 1️⃣ Foreground service branch (no active call) + + @Test + fun `when no active call should start foreground service and return FG_SERVICE`() { + // Given no active call + every { StreamVideo.instanceOrNull()?.state?.activeCall?.value } returns null + every { + ContextCompat.startForegroundService(context, any()) + } returns mockk(relaxed = true) + + // When + val result = presenter.showIncomingCall( + context = context, + callId = callId, + callDisplayName = "Caller", + callServiceConfiguration = callServiceConfig, + notification = notification, + ) + + // Then + verify { ContextCompat.startForegroundService(context, any()) } + Assert.assertEquals(ShowIncomingCallResult.FG_SERVICE, result) + } + + // endregion + + // region 2️⃣ Normal service branch (active call exists) + + @Test + fun `when active call exists should start normal service and return SERVICE`() { + every { StreamVideo.instanceOrNull()?.state?.activeCall?.value } returns mockk(relaxed = true) + + val intent = mockk(relaxed = true) + every { serviceIntentBuilder.buildStartIntent(any(), any()) } returns intent + + val result = presenter.showIncomingCall( + context = context, + callId = callId, + callDisplayName = "TestCaller", + callServiceConfiguration = callServiceConfig, + notification = notification, + ) + + verify { context.startService(any()) } + Assert.assertEquals(ShowIncomingCallResult.SERVICE, result) + } + + // endregion + + // region 3️⃣ Error branch (service start fails → fallback to notification) + + @Test + fun `when service start fails and permission granted should show notification`() { + every { streamVideoClient.state.activeCall.value } returns null + + val notificationDispatcher = mockk(relaxed = true) + every { streamVideoClient.getStreamNotificationDispatcher() } returns notificationDispatcher + + // Force exception inside safeCallWithResult + every { + ContextCompat.startForegroundService(context, any()) + } throws RuntimeException("service fail") + + // Mock permission granted + every { + ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) + } returns PackageManager.PERMISSION_GRANTED + + val result = presenter.showIncomingCall( + context, + callId, + "Caller", + callServiceConfig, + notification, + ) + + verify { + notificationDispatcher.notify( + callId, + callId.getNotificationId(NotificationType.Incoming), + notification, + ) + } + + Assert.assertEquals(ShowIncomingCallResult.ONLY_NOTIFICATION, result) + } + + // endregion + + // region 4️⃣ Error branch (service start fails, no permission) + + @Test + fun `when service start fails and no permission should return ERROR`() { + every { streamVideoClient.state.activeCall.value } returns null + + every { + ContextCompat.startForegroundService(context, any()) + } throws RuntimeException("fail") + + every { + ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) + } returns PackageManager.PERMISSION_DENIED + + val result = presenter.showIncomingCall( + context, + callId, + "Caller", + callServiceConfig, + notification, + ) + + verify(exactly = 0) { + streamVideoClient.getStreamNotificationDispatcher().notify(any(), any(), any()) + } + + Assert.assertEquals(ShowIncomingCallResult.ERROR, result) + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncherTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncherTest.kt new file mode 100644 index 00000000000..3f3dd9c2b11 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncherTest.kt @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.service + +import android.app.Notification +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.telecom.TelecomManager +import androidx.core.content.ContextCompat +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper +import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository +import io.getstream.video.android.model.StreamCallId +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.test.Test + +/** + * Focus on verifying key behaviors: + * Whether Telecom integration starts under correct conditions. + * Whether it’s skipped when conditions fail. + * Whether service launchers are called correctly for showIncomingCall() and showOutgoingCall(). + */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.TIRAMISU]) +class ServiceLauncherTest { + private lateinit var context: Context + private lateinit var telecomPermissions: TelecomPermissions + private lateinit var telecomHelper: TelecomHelper + private lateinit var incomingCallPresenter: IncomingCallPresenter + private lateinit var streamVideo: StreamVideoClient + private lateinit var serviceLauncher: ServiceLauncher + private lateinit var notification: Notification + private lateinit var callServiceConfig: CallServiceConfig + private lateinit var callId: StreamCallId + private lateinit var jetpackTelecomRepositoryProvider: JetpackTelecomRepositoryProvider + private lateinit var jetpackTelecomRepository: JetpackTelecomRepository + + private val testDispatcher = StandardTestDispatcher() + + @Before + fun setup() { + Dispatchers.setMain(testDispatcher) + + context = mockk(relaxed = true) + telecomPermissions = mockk(relaxed = true) + telecomHelper = mockk(relaxed = true) + incomingCallPresenter = mockk(relaxed = true) + streamVideo = mockk(relaxed = true) + notification = mockk(relaxed = true) + callServiceConfig = CallServiceConfig(enableTelecom = true) + callId = StreamCallId("default", "123") + jetpackTelecomRepositoryProvider = mockk(relaxed = true) + jetpackTelecomRepository = mockk(relaxed = true) + + mockkStatic(ContextCompat::class) + mockkObject(StreamVideo) + mockkConstructor(JetpackTelecomRepository::class) + mockkConstructor(JetpackTelecomRepositoryProvider::class) + mockkConstructor(IncomingCallPresenter::class) + mockkConstructor(TelecomPermissions::class) + mockkConstructor(TelecomHelper::class) + + every { + ContextCompat.checkSelfPermission( + context, + any(), + ) + } returns PackageManager.PERMISSION_GRANTED + every { anyConstructed().canUseTelecom(any(), any()) } returns true + every { anyConstructed().canUseJetpackTelecom() } returns true + every { + anyConstructed().showIncomingCall( + any(), + any(), + any(), + any(), + any(), + ) + } returns ShowIncomingCallResult.FG_SERVICE + every { + anyConstructed().get(any()) + } returns jetpackTelecomRepository + + every { StreamVideo.instanceOrNull() } returns streamVideo + every { StreamVideo.instance() } returns streamVideo + every { jetpackTelecomRepositoryProvider.get(any()) } returns jetpackTelecomRepository + + serviceLauncher = ServiceLauncher(context) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + unmockkAll() + } + + // region showIncomingCall() + + @Test + fun `showIncomingCall starts telecom registration when all conditions pass`() = runTest { + val testDispatcher = StandardTestDispatcher(testScheduler) + val testScope = TestScope(testDispatcher) + + val call = mockk(relaxed = true) + every { streamVideo.call(any(), any()) } returns call + every { call.state } returns mockk(relaxed = true) + every { call.scope } returns testScope + + mockkStatic(ContextCompat::class) + every { + ContextCompat.getSystemService( + context, + TelecomManager::class.java, + ) + } returns mockk() + + serviceLauncher.showIncomingCall( + context = context, + callId = callId, + callDisplayName = "Test Caller", + callServiceConfiguration = callServiceConfig, + isVideo = true, + payload = emptyMap(), + streamVideo = streamVideo, + notification = notification, + ) + testScheduler.advanceUntilIdle() + + coVerify { jetpackTelecomRepository.registerCall(any(), any(), true, any()) } + } + + @Test + fun `showIncomingCall skips telecom when permissions fail`() = runTest { + every { anyConstructed().canUseTelecom(any(), any()) } returns false + + serviceLauncher.showIncomingCall( + context, + callId, + "Test Caller", + callServiceConfig, + isVideo = false, + payload = emptyMap(), + streamVideo = streamVideo, + notification = notification, + ) + + coVerify(exactly = 0) { jetpackTelecomRepository.registerCall(any(), any(), any(), any()) } + } + + // +// // endregion +// +// // region showOutgoingCall() +// + @Test + fun `showOutgoingCall launches foreground service and registers telecom`() = runTest { + val testDispatcher = StandardTestDispatcher(testScheduler) + val testScope = TestScope(testDispatcher) + + val call = mockk(relaxed = true) + every { streamVideo.call(any(), any()) } returns call + every { call.state } returns mockk(relaxed = true) + every { call.scope } returns testScope + + every { streamVideo.callServiceConfigRegistry.get(any()) } returns callServiceConfig + every { call.cid } returns "default:cid-123" + every { call.isVideoEnabled() } returns true + + serviceLauncher.showOutgoingCall(call, "outgoing_call", streamVideo) + + verify { ContextCompat.startForegroundService(context, any()) } + + testScheduler.advanceUntilIdle() + + coVerify { + jetpackTelecomRepository.registerCall( + any(), + any(), + false, + true, + ) + } + } + + // + @Test + fun `showOutgoingCall skips telecom if permissions fail`() = runTest { + val call = mockk(relaxed = true) + every { streamVideo.callServiceConfigRegistry.get(any()) } returns callServiceConfig + every { call.cid } returns "default:cid-123" + every { call.isVideoEnabled() } returns true + every { anyConstructed().canUseTelecom(any(), any()) } returns false + + serviceLauncher.showOutgoingCall(call, "outgoing_call", streamVideo) + + coVerify(exactly = 0) { jetpackTelecomRepository.registerCall(any(), any(), any(), any()) } + } + + // endregion +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallControllerTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallControllerTest.kt new file mode 100644 index 00000000000..b55320159ad --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallControllerTest.kt @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.content.Context +import io.getstream.video.android.core.Call +import io.getstream.video.android.core.CallState +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository +import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.unmockkAll +import io.mockk.verify +import kotlinx.coroutines.flow.MutableStateFlow + +// @RunWith(RobolectricTestRunner::class) +class TelecomCallControllerTest { + private lateinit var context: Context + private lateinit var call: Call + private lateinit var telecomCall: TelecomCall.Registered + private lateinit var repository: JetpackTelecomRepository + private lateinit var callState: CallState + private lateinit var telecomPermissions: TelecomPermissions + private lateinit var telecomHelper: TelecomHelper + private lateinit var controller: TelecomCallController + +// @Before + fun setup() { + mockkConstructor(TelecomPermissions::class) + mockkConstructor(TelecomHelper::class) + + context = mockk(relaxed = true) + call = mockk(relaxed = true) + telecomCall = mockk(relaxed = true) + repository = mockk(relaxed = true) + callState = mockk(relaxed = true) + telecomPermissions = mockk(relaxed = true) + telecomHelper = mockk(relaxed = true) + + // Construct the system under test + controller = TelecomCallController(context) + + // Replace internal helper behaviors + every { anyConstructed().canUseTelecom(any(), context) } returns true +// every { anyConstructed() } returns telecomPermissions + every { anyConstructed().canUseJetpackTelecom() } returns true + + // Setup repository and call + every { repository.currentCall } returns MutableStateFlow(telecomCall) + every { call.state } returns callState + every { callState.jetpackTelecomRepository } returns repository + + // Setup fake client + config + val client = mockk(relaxed = true) + every { call.client } returns client + val configRegistry = mockk(relaxed = true) + every { client.callServiceConfigRegistry } returns configRegistry + every { configRegistry.get(any()) } returns CallServiceConfig(enableTelecom = true) + } + +// @After + fun tearDown() { + unmockkAll() + } + + // region leaveCall() + +// @Test + fun `leaveCall should call processAction with Disconnect`() { + every { anyConstructed().canUseTelecom(any(), any()) } returns true + + controller.leaveCall(call) + + verify { + telecomCall.processAction(any()) + } + +// verify { +// telecomCall.processAction( +// match { +// it is TelecomCallAction.Disconnect && +// it.cause.code == DisconnectCause.LOCAL && +// it.source == InteractionSource.PHONE +// } +// ) +// } + } + +// @Test + fun `leaveCall should not call processAction if telecom unavailable`() { + every { anyConstructed().canUseTelecom(any(), any()) } returns false + + controller.leaveCall(call) + + verify(exactly = 0) { telecomCall.processAction(any()) } + } + + // endregion + + // region onAnswer() + +// @Test +// fun `onAnswer should call processAction with Answer video=false when call is video`() { +// every { call.hasCapability(OwnCapability.SendVideo) } returns true +// +// controller.onAnswer(call) +// +// verify { +// telecomCall.processAction( +// match { +// it is TelecomCallAction.Answer && it.isAudioOnly == false +// } +// ) +// } +// } +// +// @Test +// fun `onAnswer should call processAction with Answer video=true when call is audio only`() { +// every { call.hasCapability(OwnCapability.SendVideo) } returns false +// every { call.isVideoEnabled() } returns false +// +// controller.onAnswer(call) +// +// verify { +// telecomCall.processAction( +// match { +// it is TelecomCallAction.Answer && it.isAudioOnly +// } +// ) +// } +// } + +// @Test + fun `onAnswer should not call processAction if telecom not allowed`() { + every { anyConstructed().canUseTelecom(any(), any()) } returns false + + controller.onAnswer(call) + + verify(exactly = 0) { telecomCall.processAction(any()) } + } + + // endregion +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissionsTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissionsTest.kt new file mode 100644 index 00000000000..9f65fd17318 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomPermissionsTest.kt @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.notifications.internal.telecom + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.telecom.TelecomManager +import androidx.core.content.ContextCompat +import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.core.StreamVideoClient +import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkObject +import io.mockk.unmockkStatic +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.test.Test + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.TIRAMISU]) +class TelecomPermissionsTest { + + private lateinit var context: Context + private lateinit var packageManager: PackageManager + private lateinit var telecomManager: TelecomManager + private lateinit var callServiceConfig: CallServiceConfig + private lateinit var telecomPermissions: TelecomPermissions + + @Before + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + mockkStatic(ContextCompat::class) +// mockkStatic(StreamVideo::class) + mockkObject(StreamVideo) + + context = mockk(relaxed = true) +// context = RuntimeEnvironment.getApplication() +// callService = CallService() +// testCallId = StreamCallId(type = "default", id = "test-call-123") + + packageManager = mockk(relaxed = true) + telecomManager = mockk(relaxed = true) + callServiceConfig = CallServiceConfig(enableTelecom = true) + + every { context.packageManager } returns packageManager + every { context.getSystemService(Context.TELECOM_SERVICE) } returns telecomManager + + telecomPermissions = TelecomPermissions() + } + + @After + fun tearDown() { + unmockkObject(StreamVideo) + unmockkStatic(ContextCompat::class) + unmockkStatic(StreamVideo::class) + clearAllMocks() + } + + @Test + fun `supportsTelecom returns true when device has telephony and default dialer`() { + every { packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } returns true + every { telecomManager.defaultDialerPackage } returns "com.android.dialer" + + val result = telecomPermissions.supportsTelecom(context) + + assertTrue(result) + } + + @Test + fun `supportsTelecom returns false when no telephony feature`() { + every { packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } returns false + every { telecomManager.defaultDialerPackage } returns "com.android.dialer" + + val result = telecomPermissions.supportsTelecom(context) + + assertFalse(result) + } + + @Test + fun `supportsTelecom returns false when no default dialer`() { + every { packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } returns true + every { telecomManager.defaultDialerPackage } returns null + + val result = telecomPermissions.supportsTelecom(context) + + assertFalse(result) + } +// +// // endregion +// +// // region canUseTelecom() + + @Test + fun `canUseTelecom returns true when all conditions pass`() { + val client = mockk { + every { telecomConfig } returns mockk() + } + every { StreamVideo.instanceOrNull() } returns client + every { packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } returns true + every { telecomManager.defaultDialerPackage } returns "com.android.dialer" + + every { + ContextCompat.checkSelfPermission(context, Manifest.permission.MANAGE_OWN_CALLS) + } returns PackageManager.PERMISSION_GRANTED + + val result = telecomPermissions.canUseTelecom(callServiceConfig, context) + + assertTrue(result) + } + + @Test + fun `canUseTelecom returns false when telecom disabled in config`() { + val result = telecomPermissions.canUseTelecom( + callServiceConfig.copy(enableTelecom = false), + context, + ) + assertFalse(result) + } + + @Test + fun `canUseTelecom returns false when StreamVideo client not opted for telecom`() { + every { StreamVideo.instanceOrNull() } returns null + val result = telecomPermissions.canUseTelecom(callServiceConfig, context) + assertFalse(result) + } + + @Test + fun `canUseTelecom returns false when missing permission`() { + val client = mockk { every { telecomConfig } returns mockk() } + every { StreamVideo.instanceOrNull() } returns client + + every { + ContextCompat.checkSelfPermission(context, Manifest.permission.MANAGE_OWN_CALLS) + } returns PackageManager.PERMISSION_DENIED + + every { packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } returns true + every { telecomManager.defaultDialerPackage } returns "com.android.dialer" + + val result = telecomPermissions.canUseTelecom(callServiceConfig, context) + + assertFalse(result) + } + + @Test + fun `canUseTelecom returns false when device doesn't support telecom`() { + val client = mockk { every { telecomConfig } returns mockk() } + every { StreamVideo.instanceOrNull() } returns client + + every { packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } returns false + every { telecomManager.defaultDialerPackage } returns "com.android.dialer" + + every { + ContextCompat.checkSelfPermission(context, Manifest.permission.MANAGE_OWN_CALLS) + } returns PackageManager.PERMISSION_GRANTED + + val result = telecomPermissions.canUseTelecom(callServiceConfig, context) + + assertFalse(result) + } +} From c4cd9fac31b9210b6298c86dbec948fe6364cea5 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Thu, 16 Oct 2025 13:42:41 +0530 Subject: [PATCH 27/29] fix merge conflicts --- .../android/core/notifications/internal/service/CallService.kt | 2 ++ .../core/notifications/internal/service/CallServiceTest.kt | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index 67a4cc4adc0..74236a305c5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -20,6 +20,7 @@ import android.Manifest import android.annotation.SuppressLint import android.app.Notification import android.app.Service +import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager @@ -29,6 +30,7 @@ import android.os.IBinder import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import androidx.media.session.MediaButtonReceiver import io.getstream.android.video.generated.models.CallAcceptedEvent import io.getstream.android.video.generated.models.CallEndedEvent diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt index c46f0900e15..a2d646903b8 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceTest.kt @@ -29,7 +29,6 @@ import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_CID import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_DISPLAY_NAME -import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_KEY import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL @@ -42,7 +41,6 @@ import io.mockk.MockKAnnotations import io.mockk.clearAllMocks import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkStatic import org.junit.After From 36670598d7d130998fb42d613674f7d1f2ed631d Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Tue, 21 Oct 2025 09:22:00 +0200 Subject: [PATCH 28/29] Merge --- .../android/core/notifications/internal/service/CallService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index 7cabebe5a72..86290a49125 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -179,7 +179,7 @@ internal open class CallService : Service() { private var isToggleCameraBroadcastReceiverRegistered = false // Call sounds - private var callSoundPlayer: CallSoundAndVibrationPlayer? = null + private var callSoundAndVibrationPlayer: CallSoundAndVibrationPlayer? = null private val serviceNotificationRetriever = ServiceNotificationRetriever() internal companion object { From 7cee7f8c92c9e84a67b30ac853534cdfc2d496e0 Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Tue, 21 Oct 2025 15:16:39 +0200 Subject: [PATCH 29/29] new api dump --- .../api/stream-video-android-core.api | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index ead06588aeb..9397c1db1fe 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -8046,17 +8046,18 @@ public final class io/getstream/video/android/core/StreamVideoBuilder { public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;)V public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;)V public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Z)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;I)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;J)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZ)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZ)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZ)V - public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;)V - public synthetic fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;Z)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;I)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;J)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZ)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZ)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZ)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;)V + public synthetic fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lio/getstream/video/android/core/StreamVideo; } @@ -13046,6 +13047,27 @@ public abstract interface class io/getstream/video/android/core/sounds/MutedRing public abstract fun getPlayOutgoingSoundIfMuted ()Z } +public final class io/getstream/video/android/core/sounds/RingingCallVibrationConfig { + public fun ()V + public fun ([JZ)V + public synthetic fun ([JZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()[J + public final fun component2 ()Z + public final fun copy ([JZ)Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig; + public static synthetic fun copy$default (Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;[JZILjava/lang/Object;)Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getEnabled ()Z + public final fun getVibratePattern ()[J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/sounds/RingingCallVibrationConfigKt { + public static final fun customVibrationPatternConfig ([J)Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig; + public static final fun disableVibrationConfig ()Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig; + public static final fun enableRingingCallVibrationConfig ()Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig; +} + public abstract interface class io/getstream/video/android/core/sounds/RingingConfig { public abstract fun getIncomingCallSoundUri ()Landroid/net/Uri; public abstract fun getOutgoingCallSoundUri ()Landroid/net/Uri;