From b74049b5969298c95403ea254f47e43cd9cc50c6 Mon Sep 17 00:00:00 2001 From: Elliot Mawby Date: Thu, 6 Jun 2024 10:34:57 -0700 Subject: [PATCH 1/7] Poll for the permission status every second We will track the permission state as enabled and fire the handler when the state changes Use a constant from the ConfigModel to control the permission polling interval --- .../core/internal/config/ConfigModel.kt | 11 ++++- .../impl/NotificationPermissionController.kt | 41 +++++++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt index d43af8a32d..05d288f931 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt @@ -193,11 +193,20 @@ class ConfigModel : Model() { * The minimum number of milliseconds required to pass to allow the fetching of IAM to occur. */ var fetchIAMMinInterval: Long - get() = getLongProperty(::fetchIAMMinInterval.name) { 30000 } + get() = getLongProperty(::fetchIAMMinInterval.name) { 30_000 } set(value) { setLongProperty(::fetchIAMMinInterval.name, value) } + /** + * The number of milliseconds between fetching the current notification permission value + */ + var fetchNotificationPermissionInterval: Long + get() = getLongProperty(::fetchNotificationPermissionInterval.name) { 1_000 } + set(value) { + setLongProperty(::fetchNotificationPermissionInterval.name, value) + } + /** * The google project number for GMS devices. */ diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt index b60f7700ac..c8f39f1c5f 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt @@ -34,6 +34,7 @@ import com.onesignal.common.events.EventProducer import com.onesignal.common.threading.WaiterWithValue import com.onesignal.core.internal.application.ApplicationLifecycleHandlerBase import com.onesignal.core.internal.application.IApplicationService +import com.onesignal.core.internal.config.ConfigModelStore import com.onesignal.core.internal.permissions.AlertDialogPrepromptForAndroidSettings import com.onesignal.core.internal.permissions.IRequestPermissionService import com.onesignal.core.internal.preferences.IPreferencesService @@ -43,6 +44,10 @@ import com.onesignal.notifications.R import com.onesignal.notifications.internal.common.NotificationHelper import com.onesignal.notifications.internal.permissions.INotificationPermissionChangedHandler import com.onesignal.notifications.internal.permissions.INotificationPermissionController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.yield internal class NotificationPermissionController( @@ -50,10 +55,13 @@ internal class NotificationPermissionController( private val _requestPermission: IRequestPermissionService, private val _applicationService: IApplicationService, private val _preferenceService: IPreferencesService, + private val _configModelStore: ConfigModelStore, ) : IRequestPermissionService.PermissionCallback, INotificationPermissionController { private val waiter = WaiterWithValue() private val events = EventProducer() + private var enabled: Boolean + private val coroutineScope = CoroutineScope(newSingleThreadContext(name = "NotificationPermissionController")) override val canRequestPermission: Boolean get() = @@ -64,7 +72,22 @@ internal class NotificationPermissionController( )!! init { + this.enabled = notificationsEnabled() _requestPermission.registerAsCallback(PERMISSION_TYPE, this) + coroutineScope.launch { + pollForPermission() + } + } + + private suspend fun pollForPermission() { + while (true) { + val enabled = this.notificationsEnabled() + if (this.enabled != enabled) { // If the permission has changed without prompting through OneSignal + this.enabled = enabled + events.fire { it.onNotificationPermissionChanged(enabled) } + } + delay(_configModelStore.model.fetchNotificationPermissionInterval) + } } @ChecksSdkIntAtLeast(api = 33) @@ -72,6 +95,12 @@ internal class NotificationPermissionController( Build.VERSION.SDK_INT > 32 && AndroidUtils.getTargetSdkVersion(_application.appContext) > 32 + private fun permissionPromptCompleted(enabled: Boolean) { + this.enabled = enabled + waiter.wake(enabled) + events.fire { it.onNotificationPermissionChanged(enabled) } + } + /** * Prompt the user for notification permission. Note it is possible the application * will be killed while the permission prompt is being displayed to the user. When the @@ -119,8 +148,7 @@ internal class NotificationPermissionController( get() = events.hasSubscribers override fun onAccept() { - waiter.wake(true) - events.fire { it.onNotificationPermissionChanged(true) } + permissionPromptCompleted(true) } override fun onReject(fallbackToSettings: Boolean) { @@ -132,8 +160,7 @@ internal class NotificationPermissionController( } if (!fallbackShown) { - waiter.wake(false) - events.fire { it.onNotificationPermissionChanged(false) } + permissionPromptCompleted(false) } } @@ -154,8 +181,7 @@ internal class NotificationPermissionController( super.onFocus() _applicationService.removeApplicationLifecycleHandler(this) val hasPermission = AndroidUtils.hasPermission(ANDROID_PERMISSION_STRING, true, _applicationService) - waiter.wake(hasPermission) - events.fire { it.onNotificationPermissionChanged(hasPermission) } + permissionPromptCompleted(hasPermission) } }, ) @@ -163,8 +189,7 @@ internal class NotificationPermissionController( } override fun onDecline() { - waiter.wake(false) - events.fire { it.onNotificationPermissionChanged(false) } + permissionPromptCompleted(false) } }, ) From 7b71e1ed53c33a4da2f124496d3b8d0ecd764cb7 Mon Sep 17 00:00:00 2001 From: Elliot Mawby Date: Mon, 10 Jun 2024 14:37:14 -0700 Subject: [PATCH 2/7] Use a waiter with a lifecycle listener to change the polling period --- .../core/internal/config/ConfigModel.kt | 17 +++++++++--- .../impl/NotificationPermissionController.kt | 27 +++++++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt index 05d288f931..684bfb690b 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt @@ -199,12 +199,21 @@ class ConfigModel : Model() { } /** - * The number of milliseconds between fetching the current notification permission value + * The number of milliseconds between fetching the current notification permission value when the app is in focus */ - var fetchNotificationPermissionInterval: Long - get() = getLongProperty(::fetchNotificationPermissionInterval.name) { 1_000 } + var foregroundFetchNotificationPermissionInterval: Long + get() = getLongProperty(::foregroundFetchNotificationPermissionInterval.name) { 1_000 } set(value) { - setLongProperty(::fetchNotificationPermissionInterval.name, value) + setLongProperty(::foregroundFetchNotificationPermissionInterval.name, value) + } + + /** + * The number of milliseconds between fetching the current notification permission value when the app is out of focus + */ + var backgroundFetchNotificationPermissionInterval: Long + get() = getLongProperty(::backgroundFetchNotificationPermissionInterval.name) { 1_800_000 } + set(value) { + setLongProperty(::backgroundFetchNotificationPermissionInterval.name, value) } /** diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt index c8f39f1c5f..aaff0d53df 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt @@ -45,9 +45,9 @@ import com.onesignal.notifications.internal.common.NotificationHelper import com.onesignal.notifications.internal.permissions.INotificationPermissionChangedHandler import com.onesignal.notifications.internal.permissions.INotificationPermissionController import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.yield internal class NotificationPermissionController( @@ -59,6 +59,8 @@ internal class NotificationPermissionController( ) : IRequestPermissionService.PermissionCallback, INotificationPermissionController { private val waiter = WaiterWithValue() + private val pollingWaiter = WaiterWithValue() + private var pollingWaitInterval: Long private val events = EventProducer() private var enabled: Boolean private val coroutineScope = CoroutineScope(newSingleThreadContext(name = "NotificationPermissionController")) @@ -74,11 +76,30 @@ internal class NotificationPermissionController( init { this.enabled = notificationsEnabled() _requestPermission.registerAsCallback(PERMISSION_TYPE, this) + pollingWaitInterval = _configModelStore.model.foregroundFetchNotificationPermissionInterval + registerPollingLifecycleListener() coroutineScope.launch { pollForPermission() } } + private fun registerPollingLifecycleListener() { + _applicationService.addApplicationLifecycleHandler( + object : ApplicationLifecycleHandlerBase() { + override fun onFocus() { + super.onFocus() + pollingWaitInterval = _configModelStore.model.foregroundFetchNotificationPermissionInterval + pollingWaiter.wake(true) + } + + override fun onUnfocused() { + super.onUnfocused() + pollingWaitInterval = _configModelStore.model.backgroundFetchNotificationPermissionInterval + } + }, + ) + } + private suspend fun pollForPermission() { while (true) { val enabled = this.notificationsEnabled() @@ -86,7 +107,9 @@ internal class NotificationPermissionController( this.enabled = enabled events.fire { it.onNotificationPermissionChanged(enabled) } } - delay(_configModelStore.model.fetchNotificationPermissionInterval) + withTimeoutOrNull(pollingWaitInterval) { + pollingWaiter.waitForWake() + } } } From daae10bb1ff5b4bd8ffe0f0df9cf46546220b4b7 Mon Sep 17 00:00:00 2001 From: Elliot Mawby Date: Thu, 13 Jun 2024 10:02:56 -0700 Subject: [PATCH 3/7] have background polling interval set to 1 day to effectively pause it Use a waiter instead of waiterWithValue --- .../java/com/onesignal/core/internal/config/ConfigModel.kt | 3 ++- .../permissions/impl/NotificationPermissionController.kt | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt index 684bfb690b..74d31c4669 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt @@ -209,9 +209,10 @@ class ConfigModel : Model() { /** * The number of milliseconds between fetching the current notification permission value when the app is out of focus + * We want this value to be very large to effectively stop polling in the background */ var backgroundFetchNotificationPermissionInterval: Long - get() = getLongProperty(::backgroundFetchNotificationPermissionInterval.name) { 1_800_000 } + get() = getLongProperty(::backgroundFetchNotificationPermissionInterval.name) { 86_400_000 } set(value) { setLongProperty(::backgroundFetchNotificationPermissionInterval.name, value) } diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt index aaff0d53df..e878258c55 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt @@ -31,6 +31,7 @@ import android.os.Build import androidx.annotation.ChecksSdkIntAtLeast import com.onesignal.common.AndroidUtils import com.onesignal.common.events.EventProducer +import com.onesignal.common.threading.Waiter import com.onesignal.common.threading.WaiterWithValue import com.onesignal.core.internal.application.ApplicationLifecycleHandlerBase import com.onesignal.core.internal.application.IApplicationService @@ -59,7 +60,7 @@ internal class NotificationPermissionController( ) : IRequestPermissionService.PermissionCallback, INotificationPermissionController { private val waiter = WaiterWithValue() - private val pollingWaiter = WaiterWithValue() + private val pollingWaiter = Waiter() private var pollingWaitInterval: Long private val events = EventProducer() private var enabled: Boolean @@ -89,11 +90,12 @@ internal class NotificationPermissionController( override fun onFocus() { super.onFocus() pollingWaitInterval = _configModelStore.model.foregroundFetchNotificationPermissionInterval - pollingWaiter.wake(true) + pollingWaiter.wake() } override fun onUnfocused() { super.onUnfocused() + // Changing the polling interval to 1 day to effectively pause polling pollingWaitInterval = _configModelStore.model.backgroundFetchNotificationPermissionInterval } }, From 4ee02508ae39e111c0ec1c99816970b8aed35e87 Mon Sep 17 00:00:00 2001 From: Elliot Mawby Date: Thu, 13 Jun 2024 12:24:54 -0700 Subject: [PATCH 4/7] Create NotificationPermissionControllerTests Adding an initial test for the polling behavior Adding mocking for setting notifications as enabled/disabled in ShadowRoboNotificationManager Adding foregroundFetchNotificationPermissionInterval to the MockHelper configModel with a value of 1 ms --- .../NotificationPermissionControllerTests.kt | 60 +++++++++++++++++++ .../shadows/ShadowRoboNotificationManager.kt | 4 ++ .../java/com/onesignal/mocks/MockHelper.kt | 1 + 3 files changed, 65 insertions(+) create mode 100644 OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt new file mode 100644 index 0000000000..efc124c592 --- /dev/null +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt @@ -0,0 +1,60 @@ +package com.onesignal.notifications.internal.permission + +import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest +import com.onesignal.core.internal.permissions.IRequestPermissionService +import com.onesignal.core.internal.preferences.IPreferencesService +import com.onesignal.debug.LogLevel +import com.onesignal.debug.internal.logging.Logging +import com.onesignal.mocks.AndroidMockHelper +import com.onesignal.mocks.MockHelper +import com.onesignal.notifications.internal.permissions.INotificationPermissionChangedHandler +import com.onesignal.notifications.internal.permissions.impl.NotificationPermissionController +import com.onesignal.notifications.shadows.ShadowRoboNotificationManager +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.runs +import kotlinx.coroutines.delay +import org.robolectric.annotation.Config + +@Config( + packageName = "com.onesignal.example", + shadows = [ShadowRoboNotificationManager::class], + sdk = [33], +) +@RobolectricTest +class NotificationPermissionControllerTests : FunSpec({ + beforeAny { + Logging.logLevel = LogLevel.NONE + ShadowRoboNotificationManager.reset() + } + + test("NotificationPermissionController permission polling fires permission changed event") { + // Given + val mockRequestPermissionService = mockk() + every { mockRequestPermissionService.registerAsCallback(any(), any()) } just runs + val mockPreferenceService = mockk() + + var handlerFired = false + val notificationPermissionController = NotificationPermissionController(AndroidMockHelper.applicationService(), mockRequestPermissionService, AndroidMockHelper.applicationService(), mockPreferenceService, MockHelper.configModelStore()) + + notificationPermissionController.subscribe( + object : INotificationPermissionChangedHandler { + override fun onNotificationPermissionChanged(enabled: Boolean) { + handlerFired = true + } + }, + ) + + // When + // permission changes + ShadowRoboNotificationManager.setNotificationsEnabled(false) + delay(5) + + // Then + // permissionChanged Event should fire + handlerFired shouldBe true + } +}) diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt index bb6cec0b78..7022904cd9 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt @@ -117,6 +117,10 @@ class ShadowRoboNotificationManager : ShadowNotificationManager() { return notifications } + fun setNotificationsEnabled(enabled: Boolean) { + mInstance.setNotificationsEnabled(enabled) + } + var lastChannel: NotificationChannel? = null var lastChannelGroup: NotificationChannelGroup? = null } diff --git a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/MockHelper.kt b/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/MockHelper.kt index b47959fce0..d8fa8ed86a 100644 --- a/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/MockHelper.kt +++ b/OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/MockHelper.kt @@ -49,6 +49,7 @@ object MockHelper { configModel.opRepoPostCreateDelay = 1 configModel.opRepoPostCreateRetryUpTo = 1 configModel.opRepoDefaultFailRetryBackoff = 1 + configModel.foregroundFetchNotificationPermissionInterval = 1 configModel.appId = DEFAULT_APP_ID From 8feeda81735dd44a5e5f432f05cb22f373f75839 Mon Sep 17 00:00:00 2001 From: Elliot Mawby Date: Thu, 13 Jun 2024 12:42:56 -0700 Subject: [PATCH 5/7] Add tests for pausing and resuming polling based on app focus --- .../NotificationPermissionControllerTests.kt | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt index efc124c592..f5ff0260a7 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt @@ -1,6 +1,9 @@ package com.onesignal.notifications.internal.permission +import androidx.test.core.app.ApplicationProvider import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest +import com.onesignal.core.internal.application.IApplicationLifecycleHandler +import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.permissions.IRequestPermissionService import com.onesignal.core.internal.preferences.IPreferencesService import com.onesignal.debug.LogLevel @@ -57,4 +60,85 @@ class NotificationPermissionControllerTests : FunSpec({ // permissionChanged Event should fire handlerFired shouldBe true } + + test("NotificationPermissionController permission polling pauses when app loses") { + // Given + val mockRequestPermissionService = mockk() + every { mockRequestPermissionService.registerAsCallback(any(), any()) } just runs + val mockPreferenceService = mockk() + val handlerList = mutableListOf() + val mockAppService = mockk() + every { mockAppService.addApplicationLifecycleHandler(any()) } answers { + handlerList.add(firstArg()) + } + every { mockAppService.appContext } returns ApplicationProvider.getApplicationContext() + + var handlerFired = false + val notificationPermissionController = NotificationPermissionController(mockAppService, mockRequestPermissionService, mockAppService, mockPreferenceService, MockHelper.configModelStore()) + + notificationPermissionController.subscribe( + object : INotificationPermissionChangedHandler { + override fun onNotificationPermissionChanged(enabled: Boolean) { + handlerFired = true + } + }, + ) + + // When + // the app has loses focus + for (handler in handlerList) { + handler.onUnfocused() + } + delay(5) + // the permission changes + ShadowRoboNotificationManager.setNotificationsEnabled(false) + delay(5) + + // Then + // permissionChanged Event should not fire + handlerFired shouldBe false + } + + test("NotificationPermissionController permission polling resumes when app gains focus") { + // Given + val mockRequestPermissionService = mockk() + every { mockRequestPermissionService.registerAsCallback(any(), any()) } just runs + val mockPreferenceService = mockk() + val handlerList = mutableListOf() + val mockAppService = mockk() + every { mockAppService.addApplicationLifecycleHandler(any()) } answers { + handlerList.add(firstArg()) + } + every { mockAppService.appContext } returns ApplicationProvider.getApplicationContext() + + var handlerFired = false + val notificationPermissionController = NotificationPermissionController(mockAppService, mockRequestPermissionService, mockAppService, mockPreferenceService, MockHelper.configModelStore()) + + notificationPermissionController.subscribe( + object : INotificationPermissionChangedHandler { + override fun onNotificationPermissionChanged(enabled: Boolean) { + handlerFired = true + } + }, + ) + + // When + // the app loses focus + for (handler in handlerList) { + handler.onUnfocused() + } + delay(5) + // the permission changes + ShadowRoboNotificationManager.setNotificationsEnabled(false) + delay(5) + // the app regains focus + for (handler in handlerList) { + handler.onFocus() + } + delay(5) + + // Then + // permissionChanged Event should fire + handlerFired shouldBe true + } }) From 4cb7cb107970c15b39c54a131fff4adbc0a225df Mon Sep 17 00:00:00 2001 From: Elliot Mawby Date: Fri, 14 Jun 2024 10:45:50 -0700 Subject: [PATCH 6/7] Start with polling interval as the background value This protects init in the background onFocus is called immediately if the app is in focus and wakes so we start polling correctly update unit test to account for this --- .../impl/NotificationPermissionController.kt | 2 +- .../NotificationPermissionControllerTests.kt | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt index e878258c55..38375986c7 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/permissions/impl/NotificationPermissionController.kt @@ -77,7 +77,7 @@ internal class NotificationPermissionController( init { this.enabled = notificationsEnabled() _requestPermission.registerAsCallback(PERMISSION_TYPE, this) - pollingWaitInterval = _configModelStore.model.foregroundFetchNotificationPermissionInterval + pollingWaitInterval = _configModelStore.model.backgroundFetchNotificationPermissionInterval registerPollingLifecycleListener() coroutineScope.launch { pollForPermission() diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt index f5ff0260a7..d5228c745a 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt @@ -8,7 +8,6 @@ import com.onesignal.core.internal.permissions.IRequestPermissionService import com.onesignal.core.internal.preferences.IPreferencesService import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging -import com.onesignal.mocks.AndroidMockHelper import com.onesignal.mocks.MockHelper import com.onesignal.notifications.internal.permissions.INotificationPermissionChangedHandler import com.onesignal.notifications.internal.permissions.impl.NotificationPermissionController @@ -39,9 +38,14 @@ class NotificationPermissionControllerTests : FunSpec({ val mockRequestPermissionService = mockk() every { mockRequestPermissionService.registerAsCallback(any(), any()) } just runs val mockPreferenceService = mockk() - + val focusHandlerList = mutableListOf() + val mockAppService = mockk() + every { mockAppService.addApplicationLifecycleHandler(any()) } answers { + focusHandlerList.add(firstArg()) + } + every { mockAppService.appContext } returns ApplicationProvider.getApplicationContext() var handlerFired = false - val notificationPermissionController = NotificationPermissionController(AndroidMockHelper.applicationService(), mockRequestPermissionService, AndroidMockHelper.applicationService(), mockPreferenceService, MockHelper.configModelStore()) + val notificationPermissionController = NotificationPermissionController(mockAppService, mockRequestPermissionService, mockAppService, mockPreferenceService, MockHelper.configModelStore()) notificationPermissionController.subscribe( object : INotificationPermissionChangedHandler { @@ -50,6 +54,11 @@ class NotificationPermissionControllerTests : FunSpec({ } }, ) + // call onFocus to set the proper polling interval. + // This happens when registering the lifecycle handler + for (focusHandler in focusHandlerList) { + focusHandler.onFocus() + } // When // permission changes @@ -83,6 +92,11 @@ class NotificationPermissionControllerTests : FunSpec({ } }, ) + // call onFocus to set the proper polling interval. + // This happens when registering the lifecycle handler + for (focusHandler in handlerList) { + focusHandler.onFocus() + } // When // the app has loses focus @@ -121,6 +135,11 @@ class NotificationPermissionControllerTests : FunSpec({ } }, ) + // call onFocus to set the proper polling interval. + // This happens when registering the lifecycle handler + for (focusHandler in handlerList) { + focusHandler.onFocus() + } // When // the app loses focus From 99d30946adcbbd2a231a117a832d22d1712618a1 Mon Sep 17 00:00:00 2001 From: Elliot Mawby Date: Fri, 14 Jun 2024 11:58:33 -0700 Subject: [PATCH 7/7] mock notificationsEnabled so that it can be reset between tests set delay to be 100 ms to fix flakiness --- .../NotificationPermissionControllerTests.kt | 20 +++++++++++-------- .../shadows/ShadowRoboNotificationManager.kt | 12 ++++++++++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt index d5228c745a..be1e290c69 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/permission/NotificationPermissionControllerTests.kt @@ -33,6 +33,10 @@ class NotificationPermissionControllerTests : FunSpec({ ShadowRoboNotificationManager.reset() } + beforeEach { + ShadowRoboNotificationManager.reset() + } + test("NotificationPermissionController permission polling fires permission changed event") { // Given val mockRequestPermissionService = mockk() @@ -62,8 +66,8 @@ class NotificationPermissionControllerTests : FunSpec({ // When // permission changes - ShadowRoboNotificationManager.setNotificationsEnabled(false) - delay(5) + ShadowRoboNotificationManager.setShadowNotificationsEnabled(false) + delay(100) // Then // permissionChanged Event should fire @@ -103,10 +107,10 @@ class NotificationPermissionControllerTests : FunSpec({ for (handler in handlerList) { handler.onUnfocused() } - delay(5) + delay(100) // the permission changes - ShadowRoboNotificationManager.setNotificationsEnabled(false) - delay(5) + ShadowRoboNotificationManager.setShadowNotificationsEnabled(false) + delay(100) // Then // permissionChanged Event should not fire @@ -146,10 +150,10 @@ class NotificationPermissionControllerTests : FunSpec({ for (handler in handlerList) { handler.onUnfocused() } - delay(5) + delay(100) // the permission changes - ShadowRoboNotificationManager.setNotificationsEnabled(false) - delay(5) + ShadowRoboNotificationManager.setShadowNotificationsEnabled(false) + delay(100) // the app regains focus for (handler in handlerList) { handler.onFocus() diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt index 7022904cd9..8b3e6fc66a 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/shadows/ShadowRoboNotificationManager.kt @@ -79,6 +79,14 @@ class ShadowRoboNotificationManager : ShadowNotificationManager() { super.notify(tag, id, notification) } + override fun setNotificationsEnabled(areNotificationsEnabled: Boolean) { + notificationsEnabled = areNotificationsEnabled + } + + override fun areNotificationsEnabled(): Boolean { + return notificationsEnabled + } + fun createNotificationChannel(channel: NotificationChannel?) { lastChannel = channel super.createNotificationChannel(channel as Any?) @@ -97,12 +105,14 @@ class ShadowRoboNotificationManager : ShadowNotificationManager() { var lastNotifId = 0 val notifications = LinkedHashMap() val cancelledNotifications = mutableListOf() + var notificationsEnabled = true fun reset() { notifications.clear() cancelledNotifications.clear() lastNotif = null lastNotifId = 0 + notificationsEnabled = true } private lateinit var mInstance: ShadowRoboNotificationManager @@ -117,7 +127,7 @@ class ShadowRoboNotificationManager : ShadowNotificationManager() { return notifications } - fun setNotificationsEnabled(enabled: Boolean) { + fun setShadowNotificationsEnabled(enabled: Boolean) { mInstance.setNotificationsEnabled(enabled) }