diff --git a/aconfig/settings_experience_flag_declarations.aconfig b/aconfig/settings_experience_flag_declarations.aconfig index d5caccfbb5c..e79b51560ed 100644 --- a/aconfig/settings_experience_flag_declarations.aconfig +++ b/aconfig/settings_experience_flag_declarations.aconfig @@ -17,3 +17,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "internet_preference_controller_v2" + namespace: "settings_experience" + description: "New InternetPreferenceControllerV2." + bug: "339884322" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/src/com/android/settings/network/InternetPreferenceControllerV2.kt b/src/com/android/settings/network/InternetPreferenceControllerV2.kt new file mode 100644 index 00000000000..f9d56189476 --- /dev/null +++ b/src/com/android/settings/network/InternetPreferenceControllerV2.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.network + +import android.content.Context +import androidx.lifecycle.LifecycleOwner +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) : + BasePreferenceController(context, preferenceKey) { + + private var preference: Preference? = null + + override fun getAvailabilityStatus() = + if (mContext.resources.getBoolean(R.bool.config_show_internet_settings)) AVAILABLE + else UNSUPPORTED_ON_DEVICE + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey) + } + + override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + WifiSummaryRepository(mContext).summaryFlow() + .collectLatestWithLifecycle(viewLifecycleOwner) { + preference?.summary = it + } + } +} diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index 323d9354bd2..3bc535845c1 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -25,6 +25,7 @@ import com.android.settings.R; import com.android.settings.SettingsDumpService; import com.android.settings.core.OnActivityResultListener; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.flags.Flags; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -75,9 +76,6 @@ public class NetworkDashboardFragment extends DashboardFragment implements private static List buildPreferenceControllers(Context context, Lifecycle lifecycle, LifecycleOwner lifecycleOwner) { - final InternetPreferenceController internetPreferenceController = - new InternetPreferenceController(context, lifecycle, lifecycleOwner); - final VpnPreferenceController vpnPreferenceController = new VpnPreferenceController(context); final PrivateDnsPreferenceController privateDnsPreferenceController = @@ -92,9 +90,14 @@ public class NetworkDashboardFragment extends DashboardFragment implements controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner)); controllers.add(vpnPreferenceController); - if (internetPreferenceController != null) { - controllers.add(internetPreferenceController); + + if (Flags.internetPreferenceControllerV2()) { + controllers.add( + new InternetPreferenceControllerV2(context, InternetPreferenceController.KEY)); + } else { + controllers.add(new InternetPreferenceController(context, lifecycle, lifecycleOwner)); } + controllers.add(privateDnsPreferenceController); // Start SettingsDumpService after the MobileNetworkRepository is created. diff --git a/src/com/android/settings/wifi/WifiSummaryRepository.kt b/src/com/android/settings/wifi/WifiSummaryRepository.kt new file mode 100644 index 00000000000..d81ae9a873a --- /dev/null +++ b/src/com/android/settings/wifi/WifiSummaryRepository.kt @@ -0,0 +1,91 @@ +/* + * 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 + +import android.content.Context +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.NetworkScoreManager +import android.net.wifi.WifiInfo +import android.net.wifi.WifiManager +import com.android.settingslib.R +import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow +import com.android.settingslib.wifi.WifiStatusTracker +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 +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +/** + * Repository that listeners to wifi callback and provide wifi summary flow to client. + */ +class WifiSummaryRepository( + private val context: Context, + private val wifiStatusTrackerFactory: (callback: Runnable) -> WifiStatusTracker = { callback -> + WifiStatusTracker( + context, + context.getSystemService(WifiManager::class.java), + context.getSystemService(NetworkScoreManager::class.java), + context.getSystemService(ConnectivityManager::class.java), + callback, + ) + }, +) { + + fun summaryFlow() = wifiStatusTrackerFlow() + .map { wifiStatusTracker -> wifiStatusTracker.getSummary() } + .conflate() + .flowOn(Dispatchers.Default) + + private fun WifiStatusTracker.getSummary(): String { + if (!enabled) return context.getString(com.android.settings.R.string.switch_off_text) + if (!connected) return context.getString(com.android.settings.R.string.disconnected) + val sanitizedSsid = WifiInfo.sanitizeSsid(ssid) ?: "" + if (statusLabel.isNullOrEmpty()) return sanitizedSsid + return context.getString( + R.string.preference_summary_default_combination, sanitizedSsid, statusLabel + ) + } + + private fun wifiStatusTrackerFlow(): Flow = callbackFlow { + var wifiStatusTracker: WifiStatusTracker? = null + wifiStatusTracker = wifiStatusTrackerFactory { wifiStatusTracker?.let(::trySend) } + + context.broadcastReceiverFlow(INTENT_FILTER) + .onEach { intent -> wifiStatusTracker.handleBroadcast(intent) } + .launchIn(this) + + wifiStatusTracker.setListening(true) + wifiStatusTracker.fetchInitialState() + trySend(wifiStatusTracker) + + awaitClose { wifiStatusTracker.setListening(false) } + }.conflate().flowOn(Dispatchers.Default) + + private companion object { + val INTENT_FILTER = IntentFilter().apply { + addAction(WifiManager.WIFI_STATE_CHANGED_ACTION) + addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION) + addAction(WifiManager.RSSI_CHANGED_ACTION) + } + } +} diff --git a/tests/spa_unit/src/com/android/settings/wifi/WifiSummaryRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/wifi/WifiSummaryRepositoryTest.kt new file mode 100644 index 00000000000..2f22851ba80 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/wifi/WifiSummaryRepositoryTest.kt @@ -0,0 +1,92 @@ +/* + * 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 + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.wifi.WifiStatusTracker +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +class WifiSummaryRepositoryTest { + + private val mockWifiStatusTracker = mock() + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val repository = WifiSummaryRepository(context) { mockWifiStatusTracker } + + @Test + fun summaryFlow_wifiDisabled_returnOff() = runBlocking { + mockWifiStatusTracker.enabled = false + + val summary = repository.summaryFlow().firstWithTimeoutOrNull() + + assertThat(summary).isEqualTo(context.getString(R.string.switch_off_text)) + } + + @Test + fun summaryFlow_wifiDisconnected_returnDisconnected() = runBlocking { + mockWifiStatusTracker.apply { + enabled = true + connected = false + } + + val summary = repository.summaryFlow().firstWithTimeoutOrNull() + + assertThat(summary).isEqualTo(context.getString(R.string.disconnected)) + } + + @Test + fun summaryFlow_wifiConnected_returnSsid() = runBlocking { + mockWifiStatusTracker.apply { + enabled = true + connected = true + ssid = TEST_SSID + } + + val summary = repository.summaryFlow().firstWithTimeoutOrNull() + + assertThat(summary).isEqualTo(TEST_SSID) + } + + @Test + fun summaryFlow_wifiConnectedAndWithSpeedLabel_returnSsidWithSpeedLabel() = runBlocking { + mockWifiStatusTracker.apply { + enabled = true + connected = true + ssid = TEST_SSID + statusLabel = STATUS_LABEL + } + + val summary = repository.summaryFlow().firstWithTimeoutOrNull() + + assertThat(summary).isEqualTo("$TEST_SSID / $STATUS_LABEL") + } + + private companion object { + const val TEST_SSID = "Test Ssid" + const val STATUS_LABEL = "Very Fast" + } +}