Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4d98b4fd authored by Steve Elliott's avatar Steve Elliott Committed by Android (Google) Code Review
Browse files

Merge "[kairos] Prevent network usage before builder activations" into main

parents 2dcf0a45 0822d034
Loading
Loading
Loading
Loading
+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)
    }
}
+32 −13
Original line number Diff line number Diff line
@@ -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

@@ -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
@@ -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
}