Loading src/com/android/settings/network/ConnectivityRepository.kt 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.network import android.content.Context import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback import android.net.Network import android.net.NetworkCapabilities import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn class ConnectivityRepository(context: Context) { private val connectivityManager = context.getSystemService(ConnectivityManager::class.java)!! fun networkCapabilitiesFlow(): Flow<NetworkCapabilities> = callbackFlow { val callback = object : NetworkCallback() { override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { trySend(networkCapabilities) Log.d(TAG, "onCapabilitiesChanged: $networkCapabilities") } override fun onLost(network: Network) { trySend(NetworkCapabilities()) Log.d(TAG, "onLost") } } trySend(getNetworkCapabilities()) connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } }.conflate().flowOn(Dispatchers.Default) private fun getNetworkCapabilities(): NetworkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: NetworkCapabilities() private companion object { private const val TAG = "ConnectivityRepository" } } src/com/android/settings/network/InternetPreferenceControllerV2.kt +1 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.core.BasePreferenceController import com.android.settings.wifi.WifiSummaryRepository import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle class InternetPreferenceControllerV2(context: Context, preferenceKey: String) : Loading @@ -40,7 +39,7 @@ class InternetPreferenceControllerV2(context: Context, preferenceKey: String) : } override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { WifiSummaryRepository(mContext).summaryFlow() InternetPreferenceRepository(mContext).summaryFlow() .collectLatestWithLifecycle(viewLifecycleOwner) { preference?.summary = it } Loading src/com/android/settings/network/InternetPreferenceRepository.kt 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.network import android.content.Context import android.net.NetworkCapabilities import android.net.wifi.WifiManager import android.provider.Settings import android.util.Log import com.android.settings.R import com.android.settings.wifi.WifiSummaryRepository import com.android.settings.wifi.repository.WifiRepository import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onEach @OptIn(ExperimentalCoroutinesApi::class) class InternetPreferenceRepository( private val context: Context, private val connectivityRepository: ConnectivityRepository = ConnectivityRepository(context), private val wifiSummaryRepository: WifiSummaryRepository = WifiSummaryRepository(context), private val wifiRepository: WifiRepository = WifiRepository(context), private val airplaneModeOnFlow: Flow<Boolean> = context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON), ) { fun summaryFlow(): Flow<String> = connectivityRepository.networkCapabilitiesFlow() .flatMapLatest { capabilities -> capabilities.summaryFlow() } .onEach { Log.d(TAG, "summaryFlow: $it") } .conflate() .flowOn(Dispatchers.Default) private fun NetworkCapabilities.summaryFlow(): Flow<String> { if (hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ) { for (transportType in transportTypes) { if (transportType == NetworkCapabilities.TRANSPORT_WIFI) { return wifiSummaryRepository.summaryFlow() } } } return defaultSummaryFlow() } private fun defaultSummaryFlow(): Flow<String> = combine( airplaneModeOnFlow, wifiRepository.wifiStateFlow(), ) { airplaneModeOn: Boolean, wifiState: Int -> context.getString( if (airplaneModeOn && wifiState != WifiManager.WIFI_STATE_ENABLED) { R.string.condition_airplane_title } else { R.string.networks_available } ) } private companion object { private const val TAG = "InternetPreferenceRepo" } } src/com/android/settings/wifi/repository/WifiRepository.kt 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.wifi.repository import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.wifi.WifiManager import android.util.Log import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach class WifiRepository( private val context: Context, private val wifiStateChangedActionFlow: Flow<Intent> = context.broadcastReceiverFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)), ) { fun wifiStateFlow() = wifiStateChangedActionFlow .map { intent -> intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN) } .onEach { Log.d(TAG, "wifiStateFlow: $it") } private companion object { private const val TAG = "WifiRepository" } } tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.network import android.content.Context import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback import android.net.Network import android.net.NetworkCapabilities import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.toListWithTimeout import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.stub @RunWith(AndroidJUnit4::class) class ConnectivityRepositoryTest { private var networkCallback: NetworkCallback? = null private val mockConnectivityManager = mock<ConnectivityManager> { on { registerDefaultNetworkCallback(any()) } doAnswer { networkCallback = it.arguments[0] as NetworkCallback } } private val context: Context = spy(ApplicationProvider.getApplicationContext()) { on { getSystemService(ConnectivityManager::class.java) } doReturn mockConnectivityManager } private val connectivityRepository = ConnectivityRepository(context) @Test fun networkCapabilitiesFlow_activeNetworkIsNull_noCrash() = runBlocking { mockConnectivityManager.stub { on { activeNetwork } doReturn null on { getNetworkCapabilities(null) } doReturn null } val networkCapabilities = connectivityRepository.networkCapabilitiesFlow().firstWithTimeoutOrNull()!! assertThat(networkCapabilities.transportTypes).isEmpty() } @Test fun networkCapabilitiesFlow_getInitialValue() = runBlocking { val expectedNetworkCapabilities = NetworkCapabilities.Builder().apply { addTransportType(NetworkCapabilities.TRANSPORT_WIFI) }.build() mockConnectivityManager.stub { on { getNetworkCapabilities(null) } doReturn expectedNetworkCapabilities } val actualNetworkCapabilities = connectivityRepository.networkCapabilitiesFlow().firstWithTimeoutOrNull()!! assertThat(actualNetworkCapabilities).isSameInstanceAs(expectedNetworkCapabilities) } @Test fun networkCapabilitiesFlow_getUpdatedValue() = runBlocking { val expectedNetworkCapabilities = NetworkCapabilities.Builder().apply { addTransportType(NetworkCapabilities.TRANSPORT_WIFI) }.build() val deferredList = async { connectivityRepository.networkCapabilitiesFlow().toListWithTimeout() } delay(100) networkCallback?.onCapabilitiesChanged(mock<Network>(), expectedNetworkCapabilities) assertThat(deferredList.await().last()).isSameInstanceAs(expectedNetworkCapabilities) } } Loading
src/com/android/settings/network/ConnectivityRepository.kt 0 → 100644 +63 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.network import android.content.Context import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback import android.net.Network import android.net.NetworkCapabilities import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn class ConnectivityRepository(context: Context) { private val connectivityManager = context.getSystemService(ConnectivityManager::class.java)!! fun networkCapabilitiesFlow(): Flow<NetworkCapabilities> = callbackFlow { val callback = object : NetworkCallback() { override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { trySend(networkCapabilities) Log.d(TAG, "onCapabilitiesChanged: $networkCapabilities") } override fun onLost(network: Network) { trySend(NetworkCapabilities()) Log.d(TAG, "onLost") } } trySend(getNetworkCapabilities()) connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) } }.conflate().flowOn(Dispatchers.Default) private fun getNetworkCapabilities(): NetworkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: NetworkCapabilities() private companion object { private const val TAG = "ConnectivityRepository" } }
src/com/android/settings/network/InternetPreferenceControllerV2.kt +1 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.core.BasePreferenceController import com.android.settings.wifi.WifiSummaryRepository import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle class InternetPreferenceControllerV2(context: Context, preferenceKey: String) : Loading @@ -40,7 +39,7 @@ class InternetPreferenceControllerV2(context: Context, preferenceKey: String) : } override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { WifiSummaryRepository(mContext).summaryFlow() InternetPreferenceRepository(mContext).summaryFlow() .collectLatestWithLifecycle(viewLifecycleOwner) { preference?.summary = it } Loading
src/com/android/settings/network/InternetPreferenceRepository.kt 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.network import android.content.Context import android.net.NetworkCapabilities import android.net.wifi.WifiManager import android.provider.Settings import android.util.Log import com.android.settings.R import com.android.settings.wifi.WifiSummaryRepository import com.android.settings.wifi.repository.WifiRepository import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onEach @OptIn(ExperimentalCoroutinesApi::class) class InternetPreferenceRepository( private val context: Context, private val connectivityRepository: ConnectivityRepository = ConnectivityRepository(context), private val wifiSummaryRepository: WifiSummaryRepository = WifiSummaryRepository(context), private val wifiRepository: WifiRepository = WifiRepository(context), private val airplaneModeOnFlow: Flow<Boolean> = context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON), ) { fun summaryFlow(): Flow<String> = connectivityRepository.networkCapabilitiesFlow() .flatMapLatest { capabilities -> capabilities.summaryFlow() } .onEach { Log.d(TAG, "summaryFlow: $it") } .conflate() .flowOn(Dispatchers.Default) private fun NetworkCapabilities.summaryFlow(): Flow<String> { if (hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ) { for (transportType in transportTypes) { if (transportType == NetworkCapabilities.TRANSPORT_WIFI) { return wifiSummaryRepository.summaryFlow() } } } return defaultSummaryFlow() } private fun defaultSummaryFlow(): Flow<String> = combine( airplaneModeOnFlow, wifiRepository.wifiStateFlow(), ) { airplaneModeOn: Boolean, wifiState: Int -> context.getString( if (airplaneModeOn && wifiState != WifiManager.WIFI_STATE_ENABLED) { R.string.condition_airplane_title } else { R.string.networks_available } ) } private companion object { private const val TAG = "InternetPreferenceRepo" } }
src/com/android/settings/wifi/repository/WifiRepository.kt 0 → 100644 +44 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.wifi.repository import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.wifi.WifiManager import android.util.Log import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach class WifiRepository( private val context: Context, private val wifiStateChangedActionFlow: Flow<Intent> = context.broadcastReceiverFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)), ) { fun wifiStateFlow() = wifiStateChangedActionFlow .map { intent -> intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN) } .onEach { Log.d(TAG, "wifiStateFlow: $it") } private companion object { private const val TAG = "WifiRepository" } }
tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.settings.network import android.content.Context import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback import android.net.Network import android.net.NetworkCapabilities import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.toListWithTimeout import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.stub @RunWith(AndroidJUnit4::class) class ConnectivityRepositoryTest { private var networkCallback: NetworkCallback? = null private val mockConnectivityManager = mock<ConnectivityManager> { on { registerDefaultNetworkCallback(any()) } doAnswer { networkCallback = it.arguments[0] as NetworkCallback } } private val context: Context = spy(ApplicationProvider.getApplicationContext()) { on { getSystemService(ConnectivityManager::class.java) } doReturn mockConnectivityManager } private val connectivityRepository = ConnectivityRepository(context) @Test fun networkCapabilitiesFlow_activeNetworkIsNull_noCrash() = runBlocking { mockConnectivityManager.stub { on { activeNetwork } doReturn null on { getNetworkCapabilities(null) } doReturn null } val networkCapabilities = connectivityRepository.networkCapabilitiesFlow().firstWithTimeoutOrNull()!! assertThat(networkCapabilities.transportTypes).isEmpty() } @Test fun networkCapabilitiesFlow_getInitialValue() = runBlocking { val expectedNetworkCapabilities = NetworkCapabilities.Builder().apply { addTransportType(NetworkCapabilities.TRANSPORT_WIFI) }.build() mockConnectivityManager.stub { on { getNetworkCapabilities(null) } doReturn expectedNetworkCapabilities } val actualNetworkCapabilities = connectivityRepository.networkCapabilitiesFlow().firstWithTimeoutOrNull()!! assertThat(actualNetworkCapabilities).isSameInstanceAs(expectedNetworkCapabilities) } @Test fun networkCapabilitiesFlow_getUpdatedValue() = runBlocking { val expectedNetworkCapabilities = NetworkCapabilities.Builder().apply { addTransportType(NetworkCapabilities.TRANSPORT_WIFI) }.build() val deferredList = async { connectivityRepository.networkCapabilitiesFlow().toListWithTimeout() } delay(100) networkCallback?.onCapabilitiesChanged(mock<Network>(), expectedNetworkCapabilities) assertThat(deferredList.await().last()).isSameInstanceAs(expectedNetworkCapabilities) } }