Merge "InternetPreferenceController V2 (7/7)" into main
diff --git a/src/com/android/settings/wifi/WifiSummaryRepository.kt b/src/com/android/settings/wifi/WifiSummaryRepository.kt
index d81ae9a..6e34cf5 100644
--- a/src/com/android/settings/wifi/WifiSummaryRepository.kt
+++ b/src/com/android/settings/wifi/WifiSummaryRepository.kt
@@ -17,44 +17,45 @@
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()
- .map { wifiStatusTracker -> wifiStatusTracker.getSummary() }
- .conflate()
- .flowOn(Dispatchers.Default)
+ 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)
private fun WifiStatusTracker.getSummary(): String {
if (!enabled) return context.getString(com.android.settings.R.string.switch_off_text)
@@ -62,30 +63,9 @@
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)
- }
- }
}
diff --git a/src/com/android/settings/wifi/repository/WifiPickerRepository.kt b/src/com/android/settings/wifi/repository/WifiPickerRepository.kt
new file mode 100644
index 0000000..791fa4b
--- /dev/null
+++ b/src/com/android/settings/wifi/repository/WifiPickerRepository.kt
@@ -0,0 +1,101 @@
+/*
+ * 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
+ }
+}
diff --git a/src/com/android/settings/wifi/repository/WifiStatusRepository.kt b/src/com/android/settings/wifi/repository/WifiStatusRepository.kt
new file mode 100644
index 0000000..f97ed49
--- /dev/null
+++ b/src/com/android/settings/wifi/repository/WifiStatusRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * 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)
+ }
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/wifi/WifiSummaryRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/wifi/WifiSummaryRepositoryTest.kt
index 2f22851..d7a92d6 100644
--- a/tests/spa_unit/src/com/android/settings/wifi/WifiSummaryRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/wifi/WifiSummaryRepositoryTest.kt
@@ -20,13 +20,19 @@
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 @@
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 @@
enabled = true
connected = false
}
+ val repository =
+ WifiSummaryRepository(
+ context = context,
+ wifiStatusRepository = mockWifiStatusRepository,
+ wifiPickerRepository = null,
+ )
val summary = repository.summaryFlow().firstWithTimeoutOrNull()
@@ -65,6 +88,12 @@
connected = true
ssid = TEST_SSID
}
+ val repository =
+ WifiSummaryRepository(
+ context = context,
+ wifiStatusRepository = mockWifiStatusRepository,
+ wifiPickerRepository = null,
+ )
val summary = repository.summaryFlow().firstWithTimeoutOrNull()
@@ -79,14 +108,40 @@
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"
}
}
diff --git a/tests/spa_unit/src/com/android/settings/wifi/repository/WifiPickerRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiPickerRepositoryTest.kt
new file mode 100644
index 0000000..cdcc13d
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiPickerRepositoryTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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()
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/wifi/repository/WifiStatusRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiStatusRepositoryTest.kt
new file mode 100644
index 0000000..200542e
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/wifi/repository/WifiStatusRepositoryTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.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 WifiStatusRepositoryTest {
+
+ private val mockWifiStatusTracker = mock<WifiStatusTracker>()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val repository = WifiStatusRepository(context) { mockWifiStatusTracker }
+
+ @Test
+ fun wifiStatusTrackerFlow() = runBlocking {
+ mockWifiStatusTracker.enabled = false
+
+ val wifiStatusTracker = repository.wifiStatusTrackerFlow().firstWithTimeoutOrNull()
+
+ assertThat(wifiStatusTracker).isSameInstanceAs(mockWifiStatusTracker)
+ }
+}