Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Fix foreground state emissions (DEV) #2112

Merged
merged 1 commit into from
Jan 15, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.util
import android.content.Context
import android.net.wifi.WifiManager
import android.os.PowerManager
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask
import de.rki.coronawarnapp.storage.LocalData
Expand All @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.task.common.DefaultTaskRequest
import de.rki.coronawarnapp.task.submitBlocking
import de.rki.coronawarnapp.util.device.BackgroundModeStatus
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.di.ProcessLifecycle
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
Expand All @@ -25,7 +26,8 @@ import javax.inject.Singleton
class WatchdogService @Inject constructor(
@AppContext private val context: Context,
private val taskController: TaskController,
private val backgroundModeStatus: BackgroundModeStatus
private val backgroundModeStatus: BackgroundModeStatus,
@ProcessLifecycle private val processLifecycleOwner: LifecycleOwner
) {

private val powerManager by lazy {
Expand All @@ -44,7 +46,7 @@ class WatchdogService @Inject constructor(
}

Timber.tag(TAG).v("Acquiring wakelocks for watchdog routine.")
ProcessLifecycleOwner.get().lifecycleScope.launch {
processLifecycleOwner.lifecycleScope.launch {
// A wakelock as the OS does not handle this for us like in the background job execution
val wakeLock = createWakeLock()
// A wifi lock to wake up the wifi connection in case the device is dozing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package de.rki.coronawarnapp.util.device

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.util.di.ProcessLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onCompletion
Expand All @@ -15,27 +15,29 @@ import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ForegroundState @Inject constructor() {
class ForegroundState @Inject constructor(
@ProcessLifecycle val processLifecycleOwner: LifecycleOwner
) {

val isInForeground: Flow<Boolean> by lazy {
MutableStateFlow(false).apply {
val foregroundStateUpdater = object : LifecycleObserver {
@Suppress("unused")
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {
CoronaWarnApplication.isAppInForeground = true
Timber.v("App is in the foreground")
tryEmit(true)
}

@Suppress("unused")
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
CoronaWarnApplication.isAppInForeground = false
Timber.v("App is in the background")
tryEmit(false)
}
}

val processLifecycle = ProcessLifecycleOwner.get().lifecycle
val processLifecycle = processLifecycleOwner.lifecycle
processLifecycle.addObserver(foregroundStateUpdater)
}
.onStart { Timber.v("isInForeground FLOW start") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.navigation.NavDeepLinkBuilder
import androidx.work.WorkManager
import dagger.Module
Expand Down Expand Up @@ -63,4 +65,9 @@ class AndroidModule {
@Provides
@Singleton
fun activityManager(@AppContext context: Context): ActivityManager = context.getSystemService()!!

@Provides
@Singleton
@ProcessLifecycle
fun procressLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.rki.coronawarnapp.util.di

import javax.inject.Qualifier

@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class ProcessLifecycle
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this?

Copy link
Member Author

@d4rken d4rken Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcressLifecycleOwner.get(): LifecycleOwner, the interface is generic and there are multiple types of LifecycleOwner, so they don't get mixed up in the DI graph, we qualify this specific type of LifecycleOwner with the annotation, similar to how we handle Context.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class Example constructor(@ProcessLifecycle val lifecycleOwner: LifecycleOwner)

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package de.rki.coronawarnapp.util.device

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.coroutines.test

class ForegroundStateTest : BaseTest() {

@MockK lateinit var lifecycleOwner: LifecycleOwner
lateinit var lifecycle: LifecycleRegistry

@BeforeEach
fun setup() {
MockKAnnotations.init(this)
lifecycle = LifecycleRegistry(lifecycleOwner)
every { lifecycleOwner.lifecycle } returns lifecycle
}

@AfterEach
fun teardown() {
clearAllMocks()
}

fun createInstance() = ForegroundState(
processLifecycleOwner = lifecycleOwner
)

@Test
fun `test emissions`() = runBlockingTest {
val instance = createInstance()

val testCollector = instance.isInForeground.test(startOnScope = this)

testCollector.latestValue shouldBe false

lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
testCollector.latestValue shouldBe true

lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
testCollector.latestValue shouldBe false

testCollector.cancel()
advanceUntilIdle()
}
}