Merge "InternetPreferenceController V2 (2/n)" into main
diff --git a/src/com/android/settings/network/ConnectivityRepository.kt b/src/com/android/settings/network/ConnectivityRepository.kt
new file mode 100644
index 0000000..3f9b61c
--- /dev/null
+++ b/src/com/android/settings/network/ConnectivityRepository.kt
@@ -0,0 +1,63 @@
+/*
+ * 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"
+ }
+}
diff --git a/src/com/android/settings/network/InternetPreferenceControllerV2.kt b/src/com/android/settings/network/InternetPreferenceControllerV2.kt
index f9d5618..351aca8 100644
--- a/src/com/android/settings/network/InternetPreferenceControllerV2.kt
+++ b/src/com/android/settings/network/InternetPreferenceControllerV2.kt
@@ -22,7 +22,6 @@
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) :
@@ -40,7 +39,7 @@
}
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
- WifiSummaryRepository(mContext).summaryFlow()
+ InternetPreferenceRepository(mContext).summaryFlow()
.collectLatestWithLifecycle(viewLifecycleOwner) {
preference?.summary = it
}
diff --git a/src/com/android/settings/network/InternetPreferenceRepository.kt b/src/com/android/settings/network/InternetPreferenceRepository.kt
new file mode 100644
index 0000000..30a98d7
--- /dev/null
+++ b/src/com/android/settings/network/InternetPreferenceRepository.kt
@@ -0,0 +1,82 @@
+/*
+ * 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"
+ }
+}
diff --git a/src/com/android/settings/wifi/repository/WifiRepository.kt b/src/com/android/settings/wifi/repository/WifiRepository.kt
new file mode 100644
index 0000000..77f0b1b
--- /dev/null
+++ b/src/com/android/settings/wifi/repository/WifiRepository.kt
@@ -0,0 +1,44 @@
+/*
+ * 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"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt
new file mode 100644
index 0000000..170b84d
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/ConnectivityRepositoryTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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)
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt
new file mode 100644
index 0000000..4cd65e7
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/InternetPreferenceRepositoryTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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 androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settings.wifi.WifiSummaryRepository
+import com.android.settings.wifi.repository.WifiRepository
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class InternetPreferenceRepositoryTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val mockConnectivityRepository = mock<ConnectivityRepository>()
+ private val mockWifiSummaryRepository = mock<WifiSummaryRepository>()
+ private val mockWifiRepository = mock<WifiRepository>()
+ private val airplaneModeOnFlow = MutableStateFlow(false)
+
+ private val repository = InternetPreferenceRepository(
+ context = context,
+ connectivityRepository = mockConnectivityRepository,
+ wifiSummaryRepository = mockWifiSummaryRepository,
+ wifiRepository = mockWifiRepository,
+ airplaneModeOnFlow = airplaneModeOnFlow,
+ )
+
+ @Test
+ fun summaryFlow_wifi() = runBlocking {
+ val wifiNetworkCapabilities = NetworkCapabilities.Builder().apply {
+ addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ }.build()
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(wifiNetworkCapabilities)
+ }
+ mockWifiSummaryRepository.stub {
+ on { summaryFlow() } doReturn flowOf(SUMMARY)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(SUMMARY)
+ }
+
+ @Test
+ fun summaryFlow_airplaneModeOnAndWifiOn() = runBlocking {
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
+ }
+ airplaneModeOnFlow.value = true
+ mockWifiRepository.stub {
+ on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_ENABLED)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(context.getString(R.string.networks_available))
+ }
+
+ @Test
+ fun summaryFlow_airplaneModeOnAndWifiOff() = runBlocking {
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
+ }
+ airplaneModeOnFlow.value = true
+ mockWifiRepository.stub {
+ on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(context.getString(R.string.condition_airplane_title))
+ }
+
+ @Test
+ fun summaryFlow_airplaneModeOff() = runBlocking {
+ mockConnectivityRepository.stub {
+ on { networkCapabilitiesFlow() } doReturn flowOf(NetworkCapabilities())
+ }
+ airplaneModeOnFlow.value = false
+ mockWifiRepository.stub {
+ on { wifiStateFlow() } doReturn flowOf(WifiManager.WIFI_STATE_DISABLED)
+ }
+
+ val summary = repository.summaryFlow().firstWithTimeoutOrNull()
+
+ assertThat(summary).isEqualTo(context.getString(R.string.networks_available))
+ }
+
+ private companion object {
+ const val SUMMARY = "Summary"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/wifi/repository/WifiRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiRepositoryTest.kt
new file mode 100644
index 0000000..dae3617
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiRepositoryTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.net.wifi.WifiManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class WifiRepositoryTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val mockWifiStateChangedActionFlow = flowOf(Intent().apply {
+ putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED)
+ })
+
+ private val repository = WifiRepository(context, mockWifiStateChangedActionFlow)
+
+ @Test
+ fun wifiStateFlow() = runBlocking {
+ val wifiState = repository.wifiStateFlow().firstWithTimeoutOrNull()
+
+ assertThat(wifiState).isEqualTo(WifiManager.WIFI_STATE_ENABLED)
+ }
+}