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

Commit 50ee61a8 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "InternetPreferenceController V2 (7/7)" into main

parents 2dc43106 b0acf0da
Loading
Loading
Loading
Loading
+29 −49
Original line number Diff line number Diff line
@@ -17,41 +17,42 @@
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.settings.wifi.repository.SharedConnectivityRepository
import com.android.settings.wifi.repository.WifiPickerRepository
import com.android.settings.wifi.repository.WifiStatusRepository
import com.android.settingslib.R
import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
import com.android.settingslib.wifi.WifiStatusTracker
import com.android.wifitrackerlib.HotspotNetworkEntry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
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.
 */
/** 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,
        )
    },
    private val wifiStatusRepository: WifiStatusRepository = WifiStatusRepository(context),
    private val wifiPickerRepository: WifiPickerRepository? =
        if (SharedConnectivityRepository.isDeviceConfigEnabled()) WifiPickerRepository(context)
        else null,
) {

    fun summaryFlow() = wifiStatusTrackerFlow()
    fun summaryFlow(): Flow<String> {
        if (wifiPickerRepository == null) return wifiStatusSummaryFlow()
        return combine(
            wifiStatusSummaryFlow(),
            wifiPickerRepository.connectedWifiEntryFlow(),
        ) { wifiStatusSummary, wifiEntry ->
            if (wifiEntry is HotspotNetworkEntry) wifiEntry.alternateSummary else wifiStatusSummary
        }
    }

    private fun wifiStatusSummaryFlow() =
        wifiStatusRepository
            .wifiStatusTrackerFlow()
            .map { wifiStatusTracker -> wifiStatusTracker.getSummary() }
            .conflate()
            .flowOn(Dispatchers.Default)
@@ -62,30 +63,9 @@ class WifiSummaryRepository(
        val sanitizedSsid = WifiInfo.sanitizeSsid(ssid) ?: ""
        if (statusLabel.isNullOrEmpty()) return sanitizedSsid
        return context.getString(
            R.string.preference_summary_default_combination, sanitizedSsid, statusLabel
            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)
        }
    }
}
+101 −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.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Process
import android.os.SystemClock
import android.util.Log
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
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.onEach

/** Repository that listeners to wifi picker callback and provide wifi picker flow to client. */
class WifiPickerRepository(
    private val context: Context,
    private val createWifiPickerTracker:
        (
            workerThread: HandlerThread, callback: WifiPickerTracker.WifiPickerTrackerCallback
        ) -> WifiPickerTracker =
        { workerThread, callback ->
            featureFactory.wifiTrackerLibProvider.createWifiPickerTracker(
                null,
                context,
                Handler(Looper.getMainLooper()),
                workerThread.getThreadHandler(),
                SystemClock.elapsedRealtimeClock(),
                MAX_SCAN_AGE_MILLIS,
                SCAN_INTERVAL_MILLIS,
                callback,
            )
        }
) {

    fun connectedWifiEntryFlow(): Flow<WifiEntry?> =
        callbackFlow {
                val workerThread =
                    HandlerThread(
                        /* name = */ "$TAG{${Integer.toHexString(System.identityHashCode(this))}}",
                        /* priority = */ Process.THREAD_PRIORITY_BACKGROUND,
                    )
                workerThread.start()
                var tracker: WifiPickerTracker? = null
                val callback =
                    object : WifiPickerTracker.WifiPickerTrackerCallback {
                        override fun onWifiEntriesChanged() {
                            trySend(tracker?.connectedWifiEntry)
                        }

                        override fun onWifiStateChanged() {}

                        override fun onNumSavedNetworksChanged() {}

                        override fun onNumSavedSubscriptionsChanged() {}
                    }

                tracker = createWifiPickerTracker(workerThread, callback)
                tracker.onStart()

                awaitClose {
                    tracker.onStop()
                    tracker.onDestroy()
                    workerThread.quit()
                }
            }
            .conflate()
            .onEach { Log.d(TAG, "connectedWifiEntryFlow: $it") }
            .flowOn(Dispatchers.Default)

    companion object {
        private const val TAG = "WifiPickerRepository"

        /** Max age of tracked WifiEntries */
        private const val MAX_SCAN_AGE_MILLIS: Long = 15000
        /** Interval between initiating WifiPickerTracker scans */
        private const val SCAN_INTERVAL_MILLIS: Long = 10000
    }
}
+75 −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.IntentFilter
import android.net.ConnectivityManager
import android.net.NetworkScoreManager
import android.net.wifi.WifiManager
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.onEach

/** Repository that listeners to wifi callback and provide wifi status flow to client. */
class WifiStatusRepository(
    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 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)
            }
    }
}
+56 −1
Original line number Diff line number Diff line
@@ -20,13 +20,19 @@ 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.settings.wifi.repository.WifiPickerRepository
import com.android.settings.wifi.repository.WifiStatusRepository
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.wifi.WifiStatusTracker
import com.android.wifitrackerlib.HotspotNetworkEntry
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
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub

@RunWith(AndroidJUnit4::class)
class WifiSummaryRepositoryTest {
@@ -35,11 +41,22 @@ class WifiSummaryRepositoryTest {

    private val context: Context = ApplicationProvider.getApplicationContext()

    private val repository = WifiSummaryRepository(context) { mockWifiStatusTracker }
    private val mockWifiStatusRepository =
        mock<WifiStatusRepository> {
            on { wifiStatusTrackerFlow() } doReturn flowOf(mockWifiStatusTracker)
        }

    private val mockWifiPickerRepository = mock<WifiPickerRepository>()

    @Test
    fun summaryFlow_wifiDisabled_returnOff() = runBlocking {
        mockWifiStatusTracker.enabled = false
        val repository =
            WifiSummaryRepository(
                context = context,
                wifiStatusRepository = mockWifiStatusRepository,
                wifiPickerRepository = null,
            )

        val summary = repository.summaryFlow().firstWithTimeoutOrNull()

@@ -52,6 +69,12 @@ class WifiSummaryRepositoryTest {
            enabled = true
            connected = false
        }
        val repository =
            WifiSummaryRepository(
                context = context,
                wifiStatusRepository = mockWifiStatusRepository,
                wifiPickerRepository = null,
            )

        val summary = repository.summaryFlow().firstWithTimeoutOrNull()

@@ -65,6 +88,12 @@ class WifiSummaryRepositoryTest {
            connected = true
            ssid = TEST_SSID
        }
        val repository =
            WifiSummaryRepository(
                context = context,
                wifiStatusRepository = mockWifiStatusRepository,
                wifiPickerRepository = null,
            )

        val summary = repository.summaryFlow().firstWithTimeoutOrNull()

@@ -79,14 +108,40 @@ class WifiSummaryRepositoryTest {
            ssid = TEST_SSID
            statusLabel = STATUS_LABEL
        }
        val repository =
            WifiSummaryRepository(
                context = context,
                wifiStatusRepository = mockWifiStatusRepository,
                wifiPickerRepository = null,
            )

        val summary = repository.summaryFlow().firstWithTimeoutOrNull()

        assertThat(summary).isEqualTo("$TEST_SSID / $STATUS_LABEL")
    }

    @Test
    fun summaryFlow_withWifiPickerRepository() = runBlocking {
        val hotspotNetworkEntry =
            mock<HotspotNetworkEntry> { on { alternateSummary } doReturn ALTERNATE_SUMMARY }
        mockWifiPickerRepository.stub {
            on { connectedWifiEntryFlow() } doReturn flowOf(hotspotNetworkEntry)
        }
        val repository =
            WifiSummaryRepository(
                context = context,
                wifiStatusRepository = mockWifiStatusRepository,
                wifiPickerRepository = mockWifiPickerRepository,
            )

        val summary = repository.summaryFlow().firstWithTimeoutOrNull()

        assertThat(summary).isEqualTo(ALTERNATE_SUMMARY)
    }

    private companion object {
        const val TEST_SSID = "Test Ssid"
        const val STATUS_LABEL = "Very Fast"
        const val ALTERNATE_SUMMARY = "Alternate Summary"
    }
}
+78 −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 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.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
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.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify

@RunWith(AndroidJUnit4::class)
class WifiPickerRepositoryTest {

    private val context: Context = ApplicationProvider.getApplicationContext()

    private val mockWifiPickerTracker = mock<WifiPickerTracker>()

    private var callback: WifiPickerTracker.WifiPickerTrackerCallback? = null

    private val repository =
        WifiPickerRepository(context) { _, callback ->
            this.callback = callback
            mockWifiPickerTracker
        }

    @Test
    fun connectedWifiEntryFlow_callOnStartOnStopAndOnDestroy() = runBlocking {
        repository.connectedWifiEntryFlow().firstWithTimeoutOrNull()

        verify(mockWifiPickerTracker).onStart()
        verify(mockWifiPickerTracker).onStop()
        verify(mockWifiPickerTracker).onDestroy()
    }

    @Test
    fun connectedWifiEntryFlow_initial() = runBlocking {
        val wifiEntry = repository.connectedWifiEntryFlow().firstWithTimeoutOrNull()

        assertThat(wifiEntry).isNull()
    }

    @Test
    fun connectedWifiEntryFlow_onWifiEntriesChanged() = runBlocking {
        val listDeferred = async { repository.connectedWifiEntryFlow().toListWithTimeout() }
        delay(100)

        mockWifiPickerTracker.stub { on { connectedWifiEntry } doReturn mock<WifiEntry>() }
        callback?.onWifiEntriesChanged()

        assertThat(listDeferred.await().filterNotNull()).isNotEmpty()
    }
}
Loading