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 a2e39c8c04..239a678c09 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 @@ -59,6 +59,7 @@ import io.getstream.video.android.core.notifications.extractor.DefaultNotificati import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher import io.getstream.video.android.core.notifications.style.StyleProvider +import io.getstream.video.android.core.utils.BackgroundRestrictions import io.getstream.video.android.core.utils.isAppInForeground import io.getstream.video.android.core.utils.safeCall import io.getstream.video.android.model.StreamCallId @@ -150,6 +151,7 @@ constructor( private val logger by taggedLogger("Video:StreamNotificationHandler") private val serviceLauncher = ServiceLauncher(application) private val styleProvider = StyleProvider(application) + private val batteryRestrictions = BackgroundRestrictions(application) internal fun shouldShowIncomingCallNotification( callBusyHandler: CallBusyHandler, @@ -1091,7 +1093,12 @@ constructor( logger.d { "[addHangUpAction] Adding hang up action for $callDisplayName (remoteParticipantCount=$remoteParticipantCount)" } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + /** + * CallStyle notifications can trigger + * CannotPostForegroundServiceNotificationException ("Bad notification for startForeground") + * on Android 12+ when the app is background-restricted. Fall back to a standard notification. + */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !batteryRestrictions.isRestricted()) { setStyle( styleProvider.getOutgoingCallStyle( callDisplayName, @@ -1110,7 +1117,12 @@ constructor( callDisplayName: String?, ): NotificationCompat.Builder = apply { logger.d { "[addCallActions] callDisplayName: $callDisplayName" } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + /** + * CallStyle notifications can trigger + * CannotPostForegroundServiceNotificationException ("Bad notification for startForeground") + * on Android 12+ when the app is background-restricted. Fall back to a standard notification. + */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !batteryRestrictions.isRestricted()) { setStyle( styleProvider.getIncomingCallStyle( callDisplayName, diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/BackgroundRestrictions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/BackgroundRestrictions.kt new file mode 100644 index 0000000000..0d34136f2b --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/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.utils + +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 + } + } +}