diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/badges/impl/BadgeCountUpdater.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/badges/impl/BadgeCountUpdater.kt index 6f2f8f0f0..5b0763386 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/badges/impl/BadgeCountUpdater.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/badges/impl/BadgeCountUpdater.kt @@ -3,6 +3,7 @@ package com.onesignal.notifications.internal.badges.impl import android.content.pm.PackageManager import android.os.Build import androidx.annotation.RequiresApi +import androidx.annotation.VisibleForTesting import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.database.IDatabaseProvider import com.onesignal.core.internal.database.impl.OneSignalDbContract @@ -14,11 +15,28 @@ import com.onesignal.notifications.internal.common.NotificationHelper import com.onesignal.notifications.internal.data.INotificationQueryHelper import com.onesignal.notifications.internal.limiting.INotificationLimitManager -internal class BadgeCountUpdater( +internal class BadgeCountUpdater private constructor( private val _applicationService: IApplicationService, private val _queryHelper: INotificationQueryHelper, private val _databaseProvider: IDatabaseProvider, + private val _sdkInt: Int, ) : IBadgeCountUpdater { + constructor( + applicationService: IApplicationService, + queryHelper: INotificationQueryHelper, + databaseProvider: IDatabaseProvider, + ) : this(applicationService, queryHelper, databaseProvider, Build.VERSION.SDK_INT) + + companion object { + @VisibleForTesting + internal fun createForTesting( + applicationService: IApplicationService, + queryHelper: INotificationQueryHelper, + databaseProvider: IDatabaseProvider, + sdkInt: Int, + ) = BadgeCountUpdater(applicationService, queryHelper, databaseProvider, sdkInt) + } + // Cache for manifest setting. private var badgesEnabled = -1 @@ -50,6 +68,10 @@ internal class BadgeCountUpdater( override fun update() { if (!areBadgesEnabled()) return + // On API 26+ the system handles badges via NotificationChannel, and + // ShortcutBadger can cause native SIGSEGV crashes on some OEM devices + // (e.g. Xiaomi Redmi) where the broadcast receiver has buggy native code. + if (_sdkInt >= Build.VERSION_CODES.O) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { updateStandard() } else { @@ -83,6 +105,7 @@ internal class BadgeCountUpdater( override fun updateCount(count: Int) { if (!areBadgeSettingsEnabled()) return + if (_sdkInt >= Build.VERSION_CODES.O) return try { ShortcutBadger.applyCountOrThrow(_applicationService.appContext, count) } catch (e: ShortcutBadgeException) { diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/badges/BadgeCountUpdaterTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/badges/BadgeCountUpdaterTests.kt new file mode 100644 index 000000000..25aaa7d96 --- /dev/null +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/badges/BadgeCountUpdaterTests.kt @@ -0,0 +1,89 @@ +package com.onesignal.notifications.internal.badges + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.os.Build +import com.onesignal.core.internal.application.IApplicationService +import com.onesignal.core.internal.database.IDatabaseProvider +import com.onesignal.notifications.internal.badges.impl.BadgeCountUpdater +import com.onesignal.notifications.internal.badges.impl.shortcutbadger.ShortcutBadger +import com.onesignal.notifications.internal.common.NotificationHelper +import com.onesignal.notifications.internal.data.INotificationQueryHelper +import io.kotest.core.spec.style.FunSpec +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkObject +import io.mockk.unmockkStatic +import io.mockk.verify + +private class Mocks { + val applicationService = mockk() + val queryHelper = mockk(relaxed = true) + val databaseProvider = mockk(relaxed = true) + + init { + val context = mockk() + val packageManager = mockk() + val applicationInfo = ApplicationInfo() + + every { applicationService.appContext } returns context + every { context.packageManager } returns packageManager + every { context.packageName } returns "com.onesignal.example" + every { + packageManager.getApplicationInfo("com.onesignal.example", PackageManager.GET_META_DATA) + } returns applicationInfo + } + + fun badgeCountUpdater(sdkInt: Int) = + BadgeCountUpdater.createForTesting( + applicationService, + queryHelper, + databaseProvider, + sdkInt, + ) +} + +class BadgeCountUpdaterTests : FunSpec({ + beforeEach { + mockkObject(NotificationHelper) + every { NotificationHelper.areNotificationsEnabled(any()) } returns true + mockkStatic(ShortcutBadger::class) + every { ShortcutBadger.applyCountOrThrow(any(), any()) } just Runs + } + + afterEach { + unmockkStatic(ShortcutBadger::class) + unmockkObject(NotificationHelper) + } + + test("update should not use ShortcutBadger on Android O") { + Mocks().badgeCountUpdater(Build.VERSION_CODES.O).update() + + verify(exactly = 0) { ShortcutBadger.applyCountOrThrow(any(), any()) } + } + + test("updateCount should not use ShortcutBadger on Android O") { + Mocks().badgeCountUpdater(Build.VERSION_CODES.O).updateCount(3) + + verify(exactly = 0) { ShortcutBadger.applyCountOrThrow(any(), any()) } + } + + test("updateCount should use ShortcutBadger before Android O") { + val mocks = Mocks() + val updater = + BadgeCountUpdater( + mocks.applicationService, + mocks.queryHelper, + mocks.databaseProvider, + ) + + updater.updateCount(3) + + verify(exactly = 1) { ShortcutBadger.applyCountOrThrow(any(), 3) } + } +})