Loading packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.android.systemui import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.kairos.ExperimentalKairosApi import com.android.systemui.kairos.KairosNetwork import com.android.systemui.kairos.runKairosTest import com.android.systemui.kairos.toColdConflatedFlow import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import org.junit.runner.RunWith @OptIn(ExperimentalKairosApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class KairosCoreStartableTest : SysuiTestCase() { @Test fun kairosNetwork_usedBeforeStarted() = testKosmos().useUnconfinedTestDispatcher().runKairosTest { lateinit var activatable: TestActivatable val underTest = KairosCoreStartable(applicationCoroutineScope) { setOf(activatable) } activatable = TestActivatable(underTest) // collect from the cold flow before starting the CoreStartable var collectCount = 0 testScope.backgroundScope.launch { activatable.coldFlow.collect { collectCount++ } } // start the CoreStartable underTest.start() // verify emissions are received activatable.emitEvent() assertThat(collectCount).isEqualTo(1) } private class TestActivatable(network: KairosNetwork) : KairosBuilder by kairosBuilder() { private val emitter = MutableSharedFlow<Unit>() private val events = buildEvents { emitter.toEvents() } val coldFlow = events.toColdConflatedFlow(network) suspend fun emitEvent() = emitter.emit(Unit) } } packages/SystemUI/src/com/android/systemui/KairosActivatable.kt +32 −13 Original line number Diff line number Diff line Loading @@ -19,23 +19,28 @@ package com.android.systemui import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.kairos.BuildScope import com.android.systemui.kairos.BuildSpec import com.android.systemui.kairos.Events import com.android.systemui.kairos.EventsLoop import com.android.systemui.kairos.ExperimentalKairosApi import com.android.systemui.kairos.Incremental import com.android.systemui.kairos.IncrementalLoop import com.android.systemui.kairos.KairosNetwork import com.android.systemui.kairos.RootKairosNetwork import com.android.systemui.kairos.State import com.android.systemui.kairos.StateLoop import com.android.systemui.kairos.TransactionScope import com.android.systemui.kairos.activateSpec import com.android.systemui.kairos.effect import com.android.systemui.kairos.launchKairosNetwork import com.android.systemui.kairos.launchScope import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import dagger.multibindings.Multibinds import javax.inject.Inject import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch Loading Loading @@ -176,21 +181,40 @@ private class KairosBuilderImpl @Inject constructor() : KairosBuilder { @SysUISingleton @ExperimentalKairosApi class KairosCoreStartable private constructor( private val appScope: CoroutineScope, private val activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, private val unwrappedNetwork: RootKairosNetwork, ) : CoreStartable, KairosNetwork by unwrappedNetwork { @Inject constructor( @Application private val appScope: CoroutineScope, private val kairosNetwork: KairosNetwork, private val activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, ) : CoreStartable { @Application appScope: CoroutineScope, activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, ) : this(appScope, activatables, appScope.launchKairosNetwork()) private val started = CompletableDeferred<Unit>() override fun start() { appScope.launch { kairosNetwork.activateSpec { unwrappedNetwork.activateSpec { for (activatable in activatables.get()) { launchScope { activatable.run { activate() } } } effect { started.complete(Unit) } } } } override suspend fun activateSpec(spec: BuildSpec<*>) { started.await() unwrappedNetwork.activateSpec(spec) } override suspend fun <R> transact(block: TransactionScope.() -> R): R { started.await() return unwrappedNetwork.transact(block) } } @Module Loading @@ -203,10 +227,5 @@ interface KairosCoreStartableModule { @Multibinds fun kairosActivatables(): Set<@JvmSuppressWildcards KairosActivatable> companion object { @Provides @SysUISingleton fun provideKairosNetwork(@Application scope: CoroutineScope): KairosNetwork = scope.launchKairosNetwork() } @Binds fun bindKairosNetwork(impl: KairosCoreStartable): KairosNetwork } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 com.android.systemui import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.kairos.ExperimentalKairosApi import com.android.systemui.kairos.KairosNetwork import com.android.systemui.kairos.runKairosTest import com.android.systemui.kairos.toColdConflatedFlow import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import org.junit.runner.RunWith @OptIn(ExperimentalKairosApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class KairosCoreStartableTest : SysuiTestCase() { @Test fun kairosNetwork_usedBeforeStarted() = testKosmos().useUnconfinedTestDispatcher().runKairosTest { lateinit var activatable: TestActivatable val underTest = KairosCoreStartable(applicationCoroutineScope) { setOf(activatable) } activatable = TestActivatable(underTest) // collect from the cold flow before starting the CoreStartable var collectCount = 0 testScope.backgroundScope.launch { activatable.coldFlow.collect { collectCount++ } } // start the CoreStartable underTest.start() // verify emissions are received activatable.emitEvent() assertThat(collectCount).isEqualTo(1) } private class TestActivatable(network: KairosNetwork) : KairosBuilder by kairosBuilder() { private val emitter = MutableSharedFlow<Unit>() private val events = buildEvents { emitter.toEvents() } val coldFlow = events.toColdConflatedFlow(network) suspend fun emitEvent() = emitter.emit(Unit) } }
packages/SystemUI/src/com/android/systemui/KairosActivatable.kt +32 −13 Original line number Diff line number Diff line Loading @@ -19,23 +19,28 @@ package com.android.systemui import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.kairos.BuildScope import com.android.systemui.kairos.BuildSpec import com.android.systemui.kairos.Events import com.android.systemui.kairos.EventsLoop import com.android.systemui.kairos.ExperimentalKairosApi import com.android.systemui.kairos.Incremental import com.android.systemui.kairos.IncrementalLoop import com.android.systemui.kairos.KairosNetwork import com.android.systemui.kairos.RootKairosNetwork import com.android.systemui.kairos.State import com.android.systemui.kairos.StateLoop import com.android.systemui.kairos.TransactionScope import com.android.systemui.kairos.activateSpec import com.android.systemui.kairos.effect import com.android.systemui.kairos.launchKairosNetwork import com.android.systemui.kairos.launchScope import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import dagger.multibindings.Multibinds import javax.inject.Inject import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch Loading Loading @@ -176,21 +181,40 @@ private class KairosBuilderImpl @Inject constructor() : KairosBuilder { @SysUISingleton @ExperimentalKairosApi class KairosCoreStartable private constructor( private val appScope: CoroutineScope, private val activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, private val unwrappedNetwork: RootKairosNetwork, ) : CoreStartable, KairosNetwork by unwrappedNetwork { @Inject constructor( @Application private val appScope: CoroutineScope, private val kairosNetwork: KairosNetwork, private val activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, ) : CoreStartable { @Application appScope: CoroutineScope, activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, ) : this(appScope, activatables, appScope.launchKairosNetwork()) private val started = CompletableDeferred<Unit>() override fun start() { appScope.launch { kairosNetwork.activateSpec { unwrappedNetwork.activateSpec { for (activatable in activatables.get()) { launchScope { activatable.run { activate() } } } effect { started.complete(Unit) } } } } override suspend fun activateSpec(spec: BuildSpec<*>) { started.await() unwrappedNetwork.activateSpec(spec) } override suspend fun <R> transact(block: TransactionScope.() -> R): R { started.await() return unwrappedNetwork.transact(block) } } @Module Loading @@ -203,10 +227,5 @@ interface KairosCoreStartableModule { @Multibinds fun kairosActivatables(): Set<@JvmSuppressWildcards KairosActivatable> companion object { @Provides @SysUISingleton fun provideKairosNetwork(@Application scope: CoroutineScope): KairosNetwork = scope.launchKairosNetwork() } @Binds fun bindKairosNetwork(impl: KairosCoreStartable): KairosNetwork }