diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/BackgroundRestrictions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/BackgroundRestrictions.kt new file mode 100644 index 00000000000..fe00656878f --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/BackgroundRestrictions.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014-2026 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.app.ActivityManager +import android.content.Context +import android.os.Build + +internal class BackgroundRestrictions(private val context: Context) { + + fun isRestricted(): Boolean { + val am = context.getSystemService(ActivityManager::class.java) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + am.isBackgroundRestricted ?: false + } else { + false + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultStreamIntentResolver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultStreamIntentResolver.kt index ed075073d6e..f43bd47e8a5 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultStreamIntentResolver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultStreamIntentResolver.kt @@ -367,10 +367,6 @@ public class DefaultStreamIntentResolver( resolveInfo.activityInfo.name, ) putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId) - putExtra( - NotificationHandler.INTENT_EXTRA_NOTIFICATION_ID, - callId.cid, - ) } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt index cf9ae85a0f3..7f0c9890b14 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/NotificationHandler.kt @@ -70,6 +70,11 @@ public interface NotificationHandler : const val INTENT_EXTRA_CALL_DISPLAY_NAME: String = "io.getstream.video.android.intent-extra.call_displayname" + @Deprecated( + message = "Notification ids are managed internally and this extra is no longer " + + "populated. Read the active notification id from CallState.notificationIdFlow, or " + + "derive it with StreamCallId.getNotificationId(NotificationType).", + ) const val INTENT_EXTRA_NOTIFICATION_ID: String = "io.getstream.video.android.intent-extra.notification_id" 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 239a678c097..f6b3c1553d0 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 @@ -203,6 +203,7 @@ constructor( payload: Map, ) { logger.d { "[onLiveCall] callId: ${callId.id}, callDisplayName: $callDisplayName" } + // TODO: Replace StreamCallId.hashCode with StreamCallId.getNotificationId(appropriateType) val notificationId = callId.hashCode() val liveCallPendingIntent = intentResolver.searchLiveCallPendingIntent(callId, notificationId, payload) @@ -253,6 +254,7 @@ constructor( payload: Map, ) { logger.d { "[onNotification] callId: ${callId.id}, callDisplayName: $callDisplayName" } + // TODO: Replace StreamCallId.hashCode with StreamCallId.getNotificationId(appropriateType) val notificationId = callId.hashCode() val intent = intentResolver.searchNotificationCallPendingIntent( callId, @@ -745,6 +747,7 @@ constructor( logger.d { "[getSimpleOngoingCallNotification] callId: ${callId.id}, callDisplayName: $callDisplayName, isOutgoingCall: $isOutgoingCall, remoteParticipantCount: $remoteParticipantCount" } + // TODO: Replace StreamCallId.hashCode with StreamCallId.getNotificationId(appropriateType) val notificationId = callId.hashCode() // Notification ID // Intents @@ -1180,6 +1183,7 @@ constructor( }, ): Notification { logger.d { "[getMinimalMediaStyleNotification] callId: ${callId.id}" } + // TODO: Replace StreamCallId.hashCode with StreamCallId.getNotificationId(appropriateType) val notificationId = callId.hashCode() // Notification ID // Intents val onClickIntent = intentResolver.searchOngoingCallPendingIntent( diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt index 53a79ce29b4..a58aee070f7 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/receivers/LeaveCallBroadcastReceiver.kt @@ -24,7 +24,7 @@ import io.getstream.video.android.core.Call import io.getstream.video.android.core.CallLeaveReason import io.getstream.video.android.core.UserActionCause import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_LEAVE_CALL -import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_NOTIFICATION_ID +import io.getstream.video.android.model.StreamCallId /** * Used to process any pending intents that feature the [ACTION_LEAVE_CALL] action. By consuming this @@ -39,13 +39,23 @@ internal class LeaveCallBroadcastReceiver : GenericCallActionBroadcastReceiver() override suspend fun onReceive(call: Call, context: Context, intent: Intent) { logger.d { "[onReceive] #ringing; callId: ${call.id}, action: ${intent.action}" } + // A call has at most one notification; its id is stored at creation time in + // CallState.notificationIdFlow (the source of truth, replacing the deprecated + // INTENT_EXTRA_NOTIFICATION_ID extra). + val notificationId = call.state.notificationIdFlow.value + // TODO: remove this legacy notification id once nothing posts under StreamCallId.hashCode(). + val legacyNotificationId = StreamCallId.fromCallCid(call.cid).hashCode() + call.leave( CallLeaveReason.UserAction( UserActionCause.LEAVE_FROM_NOTIFICATION, ), ) - val notificationId = intent.getIntExtra(INTENT_EXTRA_NOTIFICATION_ID, 0) - logger.d { "[onReceive], notificationId: $notificationId" } - NotificationManagerCompat.from(context).cancel(notificationId) + logger.d { + "[onReceive], notificationId: $notificationId, legacyNotificationId: $legacyNotificationId" + } + val notificationManager = NotificationManagerCompat.from(context) + notificationId?.let { notificationManager.cancel(it) } + notificationManager.cancel(legacyNotificationId) } } diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/notification/AbstractNotificationActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/notification/AbstractNotificationActivity.kt index b63545333a1..be0d212607d 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/notification/AbstractNotificationActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/notification/AbstractNotificationActivity.kt @@ -22,7 +22,7 @@ import androidx.core.app.NotificationManagerCompat import androidx.lifecycle.lifecycleScope import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_CID -import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_NOTIFICATION_ID +import io.getstream.video.android.core.notifications.NotificationType import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.streamCallId import kotlinx.coroutines.launch @@ -56,7 +56,7 @@ public abstract class AbstractNotificationActivity : ComponentActivity() { lifecycleScope.launch { if (hasAcceptedCall) { - dismissIncomingCallNotifications() + dismissIncomingCallNotifications(callCid) } else { loadCallData(callCid) } @@ -74,15 +74,18 @@ public abstract class AbstractNotificationActivity : ComponentActivity() { // is Result.Success -> Unit // is Result.Failure -> finish() // } - dismissIncomingCallNotifications() + dismissIncomingCallNotifications(guid) } /** * Dismisses any notifications that might be active with a given notification ID. * Used to clear up the notification state if the call has been accepted or rejected. */ - private fun dismissIncomingCallNotifications() { - val notificationId = intent.getIntExtra(INTENT_EXTRA_NOTIFICATION_ID, 0) + private fun dismissIncomingCallNotifications(callCid: StreamCallId) { + // A call has a single notification; the incoming-call notification's id comes from the + // shared generator, so derive it from the call id and cancel it. (Replaces the deprecated + // INTENT_EXTRA_NOTIFICATION_ID extra.) + val notificationId = callCid.getNotificationId(NotificationType.Incoming) NotificationManagerCompat.from(this).cancel(notificationId) finish() }