diff options
6 files changed, 260 insertions, 7 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt new file mode 100644 index 000000000000..6c02b0d44db3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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.statusbar.pipeline + +import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo +import kotlinx.coroutines.flow.StateFlow + +/** + * Interface exposing a flow for raw connectivity information. Clients should collect on + * [rawConnectivityInfoFlow] to get updates on connectivity information. + * + * Note: [rawConnectivityInfoFlow] should be a *hot* flow, so that we only create one instance of it + * and all clients get references to the same flow. + * + * This will be used for the new status bar pipeline to compile information we need to display some + * of the icons in the RHS of the status bar. + */ +interface ConnectivityInfoCollector { + val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> +} + +/** + * An object containing all of the raw connectivity information. + * + * Importantly, all the information in this object should not be processed at all (i.e., the data + * that we receive from callbacks should be piped straight into this object and not be filtered, + * manipulated, or processed in any way). Instead, any listeners on + * [ConnectivityInfoCollector.rawConnectivityInfoFlow] can do the processing. + * + * This allows us to keep all the processing in one place which is beneficial for logging and + * debugging purposes. + */ +data class RawConnectivityInfo( + val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt new file mode 100644 index 000000000000..8d69422c7427 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.statusbar.pipeline + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilitiesRepo +import kotlinx.coroutines.CoroutineScope +import javax.inject.Inject +import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * The real implementation of [ConnectivityInfoCollector] that will collect information from all the + * relevant connectivity callbacks and compile it into [rawConnectivityInfoFlow]. + */ +@SysUISingleton +class ConnectivityInfoCollectorImpl @Inject constructor( + networkCapabilitiesRepo: NetworkCapabilitiesRepo, + @Application scope: CoroutineScope, +) : ConnectivityInfoCollector { + override val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> = + // TODO(b/238425913): Collect all the separate flows for individual raw information into + // this final flow. + networkCapabilitiesRepo.dataStream + .map { + RawConnectivityInfo(networkCapabilityInfo = it) + } + .stateIn(scope, started = Lazily, initialValue = RawConnectivityInfo()) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt index a8419145d6ed..1aae25058ba8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt @@ -19,7 +19,15 @@ package com.android.systemui.statusbar.pipeline import android.content.Context import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** * A processor that transforms raw connectivity information that we get from callbacks and turns it @@ -27,20 +35,43 @@ import javax.inject.Inject * * This will be used for the new status bar pipeline to calculate the list of icons that should be * displayed in the RHS of the status bar. + * + * Anyone can listen to [processedInfoFlow] to get updates to the processed data. */ @SysUISingleton class ConnectivityInfoProcessor @Inject constructor( + connectivityInfoCollector: ConnectivityInfoCollector, context: Context, - private val statusBarPipelineFlags: StatusBarPipelineFlags, + @Application private val scope: CoroutineScope, + statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable(context) { + // Note: This flow will not start running until a client calls `collect` on it, which means that + // [connectivityInfoCollector]'s flow will also not start anything until that `collect` call + // happens. + val processedInfoFlow: Flow<ProcessedConnectivityInfo> = + if (!statusBarPipelineFlags.isNewPipelineEnabled()) + emptyFlow() + else connectivityInfoCollector.rawConnectivityInfoFlow + .map { it.process() } + .stateIn( + scope, + started = Lazily, + initialValue = ProcessedConnectivityInfo() + ) + override fun start() { - if (statusBarPipelineFlags.isNewPipelineEnabled()) { - init() - } } - /** Initializes this processor and everything it depends on. */ - private fun init() { - // TODO(b/238425913): Register all the connectivity callbacks here. + private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo { + // TODO(b/238425913): Actually process the raw info into meaningful data. + return ProcessedConnectivityInfo(this.networkCapabilityInfo) } } + +/** + * An object containing connectivity info that has been processed into data that can be directly + * used by the status bar (and potentially other SysUI areas) to display icons. + */ +data class ProcessedConnectivityInfo( + val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(), +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 771bb0c11fb6..c4e2b732f388 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.pipeline.dagger import com.android.systemui.CoreStartable +import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector +import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor import dagger.Binds import dagger.Module @@ -30,4 +32,9 @@ abstract class StatusBarPipelineModule { @IntoMap @ClassKey(ConnectivityInfoProcessor::class) abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable + + @Binds + abstract fun provideConnectivityInfoCollector( + impl: ConnectivityInfoCollectorImpl + ): ConnectivityInfoCollector } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt new file mode 100644 index 000000000000..515a7c936f4d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 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.statusbar.pipeline + +import android.net.NetworkCapabilities +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.`when` as whenever + +@OptIn(InternalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ConnectivityInfoProcessorTest : SysuiTestCase() { + + private val statusBarPipelineFlags = mock<StatusBarPipelineFlags>() + + @Before + fun setUp() { + whenever(statusBarPipelineFlags.isNewPipelineEnabled()).thenReturn(true) + } + + @Test + fun collectorInfoUpdated_processedInfoAlsoUpdated() = runBlocking { + // GIVEN a processor hooked up to a collector + val scope = CoroutineScope(Dispatchers.Unconfined) + val collector = FakeConnectivityInfoCollector() + val processor = ConnectivityInfoProcessor( + collector, + context, + scope, + statusBarPipelineFlags, + ) + + var mostRecentValue: ProcessedConnectivityInfo? = null + val job = launch(start = CoroutineStart.UNDISPATCHED) { + processor.processedInfoFlow.collect { + mostRecentValue = it + } + } + + // WHEN the collector emits a value + val networkCapabilityInfo = mapOf( + 10 to NetworkCapabilityInfo(mock(), NetworkCapabilities.Builder().build()) + ) + collector.emitValue(RawConnectivityInfo(networkCapabilityInfo)) + // Because our job uses [CoroutineStart.UNDISPATCHED], it executes in the same thread as + // this test. So, our test needs to yield to let the job run. + // Note: Once we upgrade our Kotlin coroutines testing library, we won't need this. + yield() + + // THEN the processor receives it + assertThat(mostRecentValue?.networkCapabilityInfo).isEqualTo(networkCapabilityInfo) + + job.cancel() + scope.cancel() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt new file mode 100644 index 000000000000..710e5f6eacd3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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.statusbar.pipeline + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * A test-friendly implementation of [ConnectivityInfoCollector] that just emits whatever value it + * receives in [emitValue]. + */ +class FakeConnectivityInfoCollector : ConnectivityInfoCollector { + private val _rawConnectivityInfoFlow = MutableStateFlow(RawConnectivityInfo()) + override val rawConnectivityInfoFlow = _rawConnectivityInfoFlow.asStateFlow() + + suspend fun emitValue(value: RawConnectivityInfo) { + _rawConnectivityInfoFlow.emit(value) + } +} |