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)
+    }
+}