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

Commit efa1f0e3 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

InternetPreferenceController V2 (1/n)

Refactor the InternetPreferenceController, migrate to repository and
flow, run data loading on background thread.

Only add Wifi summary for now.

Bug: 339884322
Flag: com.android.settings.flags.internet_preference_controller_v2
Test: manual - on Internet
Test: unit test
Change-Id: Ibd8911bc11b24d4a7e2ef320dea4d38b9c3a864f
parent 2ac12d02
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
    }
}
+48 −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 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
            }
    }
}
+8 −5
Original line number Diff line number Diff line
@@ -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<AbstractPreferenceController> 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.
+91 −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

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<WifiStatusTracker> = 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)
        }
    }
}
+92 −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

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<WifiStatusTracker>()

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