Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ object OneSignalDispatchers {
* The known main-thread cold-start entry points:
* | Entry point | Class |
* |---|---|
* | process-start activity lifecycle registration | `core.internal.application.impl.ActivityLifecycleInitializer` |
* | `initWithContext` / `initWithContextSuspend` | `OneSignalImp` |
* | `onStartJob` | `core.services.SyncJobService` |
* | `onReceive` | `FCMBroadcastReceiver`, `NotificationDismissReceiver`, `BootUpReceiver`, `UpgradeReceiver` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.onesignal.core.internal.application.impl
import android.app.Application
import android.content.Context
import androidx.startup.Initializer
import com.onesignal.common.threading.OneSignalDispatchers
import com.onesignal.core.internal.application.IApplicationService

/**
Expand All @@ -15,10 +16,12 @@ import com.onesignal.core.internal.application.IApplicationService
* full lifecycle regardless of when init later happens.
*
* This intentionally does NOT initialize the SDK or require an App ID; it only attaches lifecycle
* observation. App ID / consent gating remains in the runtime `initWithContext` path.
* observation and starts pure dispatcher warmup. App ID / consent gating remains in the runtime
* `initWithContext` path.
*/
class ActivityLifecycleInitializer : Initializer<IApplicationService> {
override fun create(context: Context): IApplicationService {
OneSignalDispatchers.prewarm()
val applicationService = ApplicationService.getInstance()
(context.applicationContext as? Application)?.let { application ->
applicationService.attachToApplication(application)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.onesignal.core.internal.application.impl

import android.app.Application
import android.content.Context
import com.onesignal.common.threading.OneSignalDispatchers
import io.kotest.core.spec.style.FunSpec
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import io.mockk.verify
import io.mockk.verifyOrder

/**
* Verifies ActivityLifecycleInitializer.create() prewarms OneSignalDispatchers at process start,
* before it registers activity lifecycle callbacks.
*
* Intentionally self-contained (does NOT use IOMockHelper). It only needs prewarm() stubbed to a
* no-op so the real prewarm daemon isn't spawned. It must NOT install IOMockHelper's global
* launchOnSerialIO and suspendify stubs: this spec sorts lexicographically before sibling specs
* such as BackgroundManagerTests, and those stubs would leak into the shared OneSignalDispatchers
* object mock and run dispatched blocks inline in the next spec. Stubbing only prewarm() keeps the
* leak surface empty.
*/
class ActivityLifecycleInitializerTests : FunSpec({
beforeTest {
mockkObject(OneSignalDispatchers)
every { OneSignalDispatchers.prewarm() } returns Unit
resetSharedApplicationService()
}

afterTest {
unmockkObject(OneSignalDispatchers)
resetSharedApplicationService()
}

test("create prewarms before registering activity lifecycle callbacks") {
val application = mockk<Application>(relaxed = true)
every { application.applicationContext } returns application

ActivityLifecycleInitializer().create(application)

verify(exactly = 1) { OneSignalDispatchers.prewarm() }
verify(exactly = 1) {
application.registerActivityLifecycleCallbacks(any<Application.ActivityLifecycleCallbacks>())
}
verifyOrder {
OneSignalDispatchers.prewarm()
application.registerActivityLifecycleCallbacks(any<Application.ActivityLifecycleCallbacks>())
}
}

test("create prewarms even when application context is unavailable") {
val context = mockk<Context>(relaxed = true)
every { context.applicationContext } returns context

ActivityLifecycleInitializer().create(context)

verify(exactly = 1) { OneSignalDispatchers.prewarm() }
}
})

private fun resetSharedApplicationService() {
ApplicationService::class.java.getDeclaredField("sharedInstance").apply {
isAccessible = true
set(null, null)
}
}
Loading