From 84672864c66d841efdc8cad007082ad3f4330975 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Wed, 17 Jun 2026 20:00:52 +0530 Subject: [PATCH 1/6] temp: force-commit --- .../BatteryRestrictionsDetector.kt | 33 +++++++++++++++++++ .../StreamDefaultNotificationHandler.kt | 6 ++-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.kt new file mode 100644 index 0000000000..c282db3c94 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.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.notifications + +import android.app.ActivityManager +import android.content.Context +import android.os.Build + +internal class BatteryRestrictionsDetector(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 + } else { + false + } + } +} 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..04fd5d8dff 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 @@ -45,6 +45,7 @@ import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi +import io.getstream.video.android.core.notifications.BatteryRestrictionsDetector import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver import io.getstream.video.android.core.notifications.DefaultStreamIntentResolver import io.getstream.video.android.core.notifications.IncomingNotificationAction @@ -150,6 +151,7 @@ constructor( private val logger by taggedLogger("Video:StreamNotificationHandler") private val serviceLauncher = ServiceLauncher(application) private val styleProvider = StyleProvider(application) + private val batteryRestrictionsDetector = BatteryRestrictionsDetector(application) internal fun shouldShowIncomingCallNotification( callBusyHandler: CallBusyHandler, @@ -1091,7 +1093,7 @@ constructor( logger.d { "[addHangUpAction] Adding hang up action for $callDisplayName (remoteParticipantCount=$remoteParticipantCount)" } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && batteryRestrictionsDetector.isRestricted()) { setStyle( styleProvider.getOutgoingCallStyle( callDisplayName, @@ -1110,7 +1112,7 @@ constructor( callDisplayName: String?, ): NotificationCompat.Builder = apply { logger.d { "[addCallActions] callDisplayName: $callDisplayName" } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && batteryRestrictionsDetector.isRestricted()) { setStyle( styleProvider.getIncomingCallStyle( callDisplayName, From 9e7d1029c581517bd41f8f064c0c21f7b6b1bca8 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 22 Jun 2026 13:20:52 +0530 Subject: [PATCH 2/6] fix: refactor --- .../handlers/StreamDefaultNotificationHandler.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 04fd5d8dff..a2e39c8c04 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 @@ -45,7 +45,6 @@ import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.call.CallBusyHandler import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi -import io.getstream.video.android.core.notifications.BatteryRestrictionsDetector import io.getstream.video.android.core.notifications.DefaultNotificationIntentBundleResolver import io.getstream.video.android.core.notifications.DefaultStreamIntentResolver import io.getstream.video.android.core.notifications.IncomingNotificationAction @@ -151,7 +150,6 @@ constructor( private val logger by taggedLogger("Video:StreamNotificationHandler") private val serviceLauncher = ServiceLauncher(application) private val styleProvider = StyleProvider(application) - private val batteryRestrictionsDetector = BatteryRestrictionsDetector(application) internal fun shouldShowIncomingCallNotification( callBusyHandler: CallBusyHandler, @@ -1093,7 +1091,7 @@ constructor( logger.d { "[addHangUpAction] Adding hang up action for $callDisplayName (remoteParticipantCount=$remoteParticipantCount)" } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && batteryRestrictionsDetector.isRestricted()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { setStyle( styleProvider.getOutgoingCallStyle( callDisplayName, @@ -1112,7 +1110,7 @@ constructor( callDisplayName: String?, ): NotificationCompat.Builder = apply { logger.d { "[addCallActions] callDisplayName: $callDisplayName" } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && batteryRestrictionsDetector.isRestricted()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { setStyle( styleProvider.getIncomingCallStyle( callDisplayName, From 93fc2cf596627adf56a743885c74895ac6148a4b Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 22 Jun 2026 13:35:09 +0530 Subject: [PATCH 3/6] fix: remove unused classes --- .../BatteryRestrictionsDetector.kt | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.kt diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.kt deleted file mode 100644 index c282db3c94..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/BatteryRestrictionsDetector.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.notifications - -import android.app.ActivityManager -import android.content.Context -import android.os.Build - -internal class BatteryRestrictionsDetector(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 - } else { - false - } - } -} From 5cb7eb1d27d9c64c20d6f625adee5e75d72e0a1e Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 22 Jun 2026 16:06:02 +0530 Subject: [PATCH 4/6] fix: Fix a crash when application put to background restriction by rendering normal notification instead of CallStyle notification --- .../android/core/BackgroundRestrictions.kt | 33 +++++++++++++++++++ .../StreamDefaultNotificationHandler.kt | 16 +++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/BackgroundRestrictions.kt 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 0000000000..87c80c3e0b --- /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 + } else { + false + } + } +} 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..63f52ec022 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 @@ -36,6 +36,7 @@ import io.getstream.android.push.permissions.DefaultNotificationPermissionHandle import io.getstream.android.push.permissions.NotificationPermissionHandler import io.getstream.android.video.generated.models.LocalCallMissedEvent import io.getstream.log.taggedLogger +import io.getstream.video.android.core.BackgroundRestrictions import io.getstream.video.android.core.Call import io.getstream.video.android.core.MemberState import io.getstream.video.android.core.ParticipantState @@ -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, From a0f1e7cefe3ebf1af0f5dae6256886fe676cc376 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Mon, 22 Jun 2026 16:43:28 +0530 Subject: [PATCH 5/6] fix: add safe null-check --- .../io/getstream/video/android/core/BackgroundRestrictions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 87c80c3e0b..fe00656878 100644 --- 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 @@ -25,7 +25,7 @@ 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 + am.isBackgroundRestricted ?: false } else { false } From 900abe07a571778b7d7b94242f0c0524f27e34d2 Mon Sep 17 00:00:00 2001 From: rahullohra Date: Tue, 23 Jun 2026 14:31:04 +0530 Subject: [PATCH 6/6] fix: moved file to utils --- .../notifications/handlers/StreamDefaultNotificationHandler.kt | 2 +- .../video/android/core/{ => utils}/BackgroundRestrictions.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/{ => utils}/BackgroundRestrictions.kt (95%) 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 63f52ec022..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 @@ -36,7 +36,6 @@ import io.getstream.android.push.permissions.DefaultNotificationPermissionHandle import io.getstream.android.push.permissions.NotificationPermissionHandler import io.getstream.android.video.generated.models.LocalCallMissedEvent import io.getstream.log.taggedLogger -import io.getstream.video.android.core.BackgroundRestrictions import io.getstream.video.android.core.Call import io.getstream.video.android.core.MemberState import io.getstream.video.android.core.ParticipantState @@ -60,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 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/utils/BackgroundRestrictions.kt similarity index 95% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/BackgroundRestrictions.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/BackgroundRestrictions.kt index fe00656878..0d34136f2b 100644 --- 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/utils/BackgroundRestrictions.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core +package io.getstream.video.android.core.utils import android.app.ActivityManager import android.content.Context