From 1adda683ec082624a8a16aa5728e0c0d4b008e79 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Wed, 26 Jul 2023 16:31:51 +0000 Subject: [PATCH 1/6] [Status Bar] Implement wifi activity in WifiRepositoryViaTrackerLib. Bug: 292534484 Test: turn on config to show activity & turn on flag -> verify wifi indicator shows activity and updates based on callbacks Test: atest WifiRepositoryViaTrackerLibTest Change-Id: I9e297db28be85505ce80e8e491be3e8853d7244d --- .../repository/prod/WifiRepositoryHelper.kt | 80 +++++++++++++++++++ .../repository/prod/WifiRepositoryImpl.kt | 40 ++-------- .../prod/WifiRepositoryViaTrackerLib.kt | 15 +++- .../data/repository/FakeWifiRepository.kt | 2 +- .../prod/WifiRepositoryViaTrackerLibTest.kt | 57 +++++++++++++ 5 files changed, 156 insertions(+), 38 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt new file mode 100644 index 0000000000000..f1b98b3972e1a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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.systemui.statusbar.pipeline.wifi.data.repository.prod + +import android.net.wifi.WifiManager +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel +import java.util.concurrent.Executor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** + * Object to provide shared helper functions between [WifiRepositoryImpl] and + * [WifiRepositoryViaTrackerLib]. + */ +object WifiRepositoryHelper { + /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */ + fun createActivityFlow( + wifiManager: WifiManager, + @Main mainExecutor: Executor, + scope: CoroutineScope, + tableLogBuffer: TableLogBuffer, + inputLogger: (String) -> Unit, + ): StateFlow { + return conflatedCallbackFlow { + val callback = + WifiManager.TrafficStateCallback { state -> + inputLogger.invoke(prettyPrintActivity(state)) + trySend(state.toWifiDataActivityModel()) + } + wifiManager.registerTrafficStateCallback(mainExecutor, callback) + awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } + } + .logDiffsForTable( + tableLogBuffer, + columnPrefix = ACTIVITY_PREFIX, + initialValue = ACTIVITY_DEFAULT, + ) + .stateIn( + scope, + started = SharingStarted.WhileSubscribed(), + initialValue = ACTIVITY_DEFAULT, + ) + } + + // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer. + private fun prettyPrintActivity(activity: Int): String { + return when (activity) { + WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" + WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" + else -> "INVALID" + } + } + + private const val ACTIVITY_PREFIX = "wifiActivity" + val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 995de6d2fc615..9944c00c6ee7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -28,7 +28,6 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkRequest import android.net.wifi.WifiInfo import android.net.wifi.WifiManager -import android.net.wifi.WifiManager.TrafficStateCallback import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -40,7 +39,6 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository @@ -218,29 +216,15 @@ constructor( ) override val wifiActivity: StateFlow = - conflatedCallbackFlow { - val callback = TrafficStateCallback { state -> - logger.logActivity(prettyPrintActivity(state)) - trySend(state.toWifiDataActivityModel()) - } - wifiManager.registerTrafficStateCallback(mainExecutor, callback) - awaitClose { wifiManager.unregisterTrafficStateCallback(callback) } - } - .logDiffsForTable( - wifiTableLogBuffer, - columnPrefix = ACTIVITY_PREFIX, - initialValue = ACTIVITY_DEFAULT, - ) - .stateIn( - scope, - started = SharingStarted.WhileSubscribed(), - initialValue = ACTIVITY_DEFAULT, - ) + WifiRepositoryHelper.createActivityFlow( + wifiManager, + mainExecutor, + scope, + wifiTableLogBuffer, + logger::logActivity, + ) companion object { - private const val ACTIVITY_PREFIX = "wifiActivity" - - val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false) // Start out with no known wifi network. // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an // initial fetch to get a starting wifi network. But, it uses a deprecated API @@ -284,16 +268,6 @@ constructor( } } - private fun prettyPrintActivity(activity: Int): String { - return when (activity) { - TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE" - TrafficStateCallback.DATA_ACTIVITY_IN -> "IN" - TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT" - TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT" - else -> "INVALID" - } - } - private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest = NetworkRequest.Builder() .clearCapabilities() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index 127136789ba6d..eb294846e8ce1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -44,7 +44,6 @@ import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow @@ -208,10 +207,14 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) - // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since - // WifiTrackerLib doesn't expose activity details. override val wifiActivity: StateFlow = - MutableStateFlow(DataActivityModel(false, false)) + WifiRepositoryHelper.createActivityFlow( + wifiManager, + mainExecutor, + scope, + wifiTrackerLibTableLogBuffer, + this::logActivity, + ) private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) { inputLogger.log( @@ -231,6 +234,10 @@ constructor( ) } + private fun logActivity(activity: String) { + inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" }) + } + /** * Data class storing all the information fetched from [WifiPickerTracker]. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index 1bf431b4ea130..1c8dac14b0899 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 7002cbb6ab211..2f713cf04512c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.LogBuffer import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.concurrency.FakeExecutor @@ -45,6 +46,7 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.mockito.Mockito.verify /** * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests. @@ -643,11 +645,66 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } + @Test + fun wifiActivity_callbackGivesNone_activityFlowHasNone() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) + } + + @Test + fun wifiActivity_callbackGivesIn_activityFlowHasIn() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false)) + } + + @Test + fun wifiActivity_callbackGivesOut_activityFlowHasOut() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true)) + } + + @Test + fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiActivity) + + getTrafficStateCallback() + .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT) + + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) + } + private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback { testScope.runCurrent() return callbackCaptor.value } + private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback { + testScope.runCurrent() + val callbackCaptor = argumentCaptor() + verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + private fun createRepo(): WifiRepositoryViaTrackerLib { return WifiRepositoryViaTrackerLib( testScope.backgroundScope, From 98eb78a66ffafbea9d70ad5986b4e7299092b1db Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Thu, 27 Jul 2023 17:46:46 +0000 Subject: [PATCH 2/6] [Status Bar] WifiTrackerLib: Use WifiEntry.title for SSID. More information in the bug description about why we should be able to use `title` for SSID. Bug: 292534484 Test: flag on, switch wifi networks a few times, `adb shell dumpsys activity service com.android.systemui/.SystemUIService WifiTrackerLibTableLog` -> verify table has the wifi network names listed for `ssid` Test: atest WifiRepositoryViaTrackerLibTest Change-Id: I10921ebf76e3e60cb6d626353886d56b791890e4 --- .../prod/WifiRepositoryViaTrackerLib.kt | 9 ++- .../prod/WifiRepositoryViaTrackerLibTest.kt | 55 +++++++++++++------ 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index eb294846e8ce1..6f1ac34de9d36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -185,9 +185,12 @@ constructor( networkId = NETWORK_ID, isValidated = this.hasInternetAccess(), level = this.level, - ssid = this.ssid, - // TODO(b/292534484): Fetch the real values from [WifiEntry] (#getTitle might be - // appropriate). + ssid = this.title, + // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for + // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can + // always be false/null in this repository. + // TODO(b/292534484): Remove these fields from the wifi network model once this + // repository is fully enabled. isPasspointAccessPoint = false, isOnlineSignUpForPasspointAccessPoint = false, passpointProviderFriendlyName = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 2f713cf04512c..bd20e415ab371 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -240,7 +240,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -248,7 +248,28 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest is WifiNetworkModel.Active).isTrue() val latestActive = latest as WifiNetworkModel.Active assertThat(latestActive.level).isEqualTo(3) - assertThat(latestActive.ssid).isEqualTo(SSID) + assertThat(latestActive.ssid).isEqualTo(TITLE) + } + + @Test + fun accessPointInfo_alwaysFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(3) + whenever(this.title).thenReturn(TITLE) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.isPasspointAccessPoint).isFalse() + assertThat(latestActive.isOnlineSignUpForPasspointAccessPoint).isFalse() + assertThat(latestActive.passpointProviderFriendlyName).isNull() } @Test @@ -384,7 +405,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) - whenever(this.ssid).thenReturn("AB") + whenever(this.title).thenReturn("AB") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -399,7 +420,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(4) - whenever(this.ssid).thenReturn("CD") + whenever(this.title).thenReturn("CD") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry) getCallback().onWifiEntriesChanged() @@ -432,12 +453,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(SSID) + assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(TITLE) // WHEN we lose our current network whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) @@ -482,7 +503,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(1) - whenever(this.ssid).thenReturn(SSID) + whenever(this.title).thenReturn(TITLE) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -490,7 +511,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest1 is WifiNetworkModel.Active).isTrue() val latest1Active = latest1 as WifiNetworkModel.Active assertThat(latest1Active.level).isEqualTo(1) - assertThat(latest1Active.ssid).isEqualTo(SSID) + assertThat(latest1Active.ssid).isEqualTo(TITLE) // WHEN we add a second subscriber after having already emitted a value val latest2 by collectLastValue(underTest.wifiNetwork) @@ -499,7 +520,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest2 is WifiNetworkModel.Active).isTrue() val latest2Active = latest2 as WifiNetworkModel.Active assertThat(latest2Active.level).isEqualTo(1) - assertThat(latest2Active.ssid).isEqualTo(SSID) + assertThat(latest2Active.ssid).isEqualTo(TITLE) } @Test @@ -569,14 +590,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { */ @Test - fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(null) + whenever(this.title).thenReturn(null) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -586,14 +607,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_unknownTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(UNKNOWN_SSID) + whenever(this.title).thenReturn(UNKNOWN_SSID) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -603,14 +624,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() = + fun isWifiConnectedWithValidSsid_activeNetwork_validTitle_true() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -628,7 +649,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -717,6 +738,6 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } private companion object { - const val SSID = "AB" + const val TITLE = "AB" } } From 1a3a5b5361635e03bca5f959eac6c7482fa6e355 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Fri, 28 Jul 2023 14:52:24 +0000 Subject: [PATCH 3/6] [Status Bar] Wifi: Add support for hotspot device types to repo. This is needed for instant tether. The device type isn't hooked up to the UI yet because the device type icon should only be displayed in Quick Settings Internet Tile (not the status bar) and the Internet Tile isn't yet hooked in to the new pipeline. Bug: 265342892 Bug: 293585143 Test: `adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi show -e hotspot {tablet/laptop/watch/etc}` -> verify demo repo has hotspot info Test: atest WifiRepositoryViaTrackerLibTest WifiNetworkModelTest Change-Id: Ia121ea81717333addd6cf498100c34d3fcae2297 --- .../src/com/android/systemui/flags/Flags.kt | 3 + .../repository/demo/DemoModeWifiDataSource.kt | 17 ++ .../repository/demo/DemoWifiRepository.kt | 1 + .../demo/model/FakeWifiEventModel.kt | 3 + .../repository/prod/WifiRepositoryImpl.kt | 2 + .../prod/WifiRepositoryViaTrackerLib.kt | 17 ++ .../wifi/shared/model/WifiNetworkModel.kt | 51 ++++++ .../keyguard/CarrierTextManagerTest.java | 10 +- .../repository/prod/WifiRepositoryImplTest.kt | 20 +++ .../prod/WifiRepositoryViaTrackerLibTest.kt | 168 ++++++++++++++++-- .../wifi/shared/model/WifiNetworkModelTest.kt | 9 +- 11 files changed, 287 insertions(+), 14 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 99e33ea8e619b..27541e8099024 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -374,6 +374,9 @@ object Flags { @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON = unreleasedFlag(614, "incompatible_charging_battery_icon") + // TODO(b/293585143): Tracking Bug + val INSTANT_TETHER = unreleasedFlag(615, "instant_tether") + // 700 - dialer/calls // TODO(b/254512734): Tracking Bug val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt index 7d2501ca0e79b..ab9b516b837f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt @@ -24,6 +24,7 @@ import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK import com.android.systemui.demomode.DemoModeController import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -56,12 +57,14 @@ constructor( val activity = getString("activity").toActivity() val ssid = getString("ssid") val validated = getString("fully").toBoolean() + val hotspotDeviceType = getString("hotspot").toHotspotDeviceType() return FakeWifiEventModel.Wifi( level = level, activity = activity, ssid = ssid, validated = validated, + hotspotDeviceType, ) } @@ -82,6 +85,20 @@ constructor( else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE } + private fun String?.toHotspotDeviceType(): WifiNetworkModel.HotspotDeviceType { + return when (this) { + null, + "none" -> WifiNetworkModel.HotspotDeviceType.NONE + "unknown" -> WifiNetworkModel.HotspotDeviceType.UNKNOWN + "phone" -> WifiNetworkModel.HotspotDeviceType.PHONE + "tablet" -> WifiNetworkModel.HotspotDeviceType.TABLET + "laptop" -> WifiNetworkModel.HotspotDeviceType.LAPTOP + "watch" -> WifiNetworkModel.HotspotDeviceType.WATCH + "auto" -> WifiNetworkModel.HotspotDeviceType.AUTO + else -> WifiNetworkModel.HotspotDeviceType.INVALID + } + } + companion object { const val DEFAULT_CARRIER_MERGED_SUB_ID = 10 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index a57be665f105e..99b680056d7f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -97,6 +97,7 @@ constructor( isValidated = validated ?: true, level = level ?: 0, ssid = ssid ?: DEMO_NET_SSID, + hotspotDeviceType = hotspotDeviceType, // These fields below aren't supported in demo mode, since they aren't needed to satisfy // the interface. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt index f5035cbc02154..b2e843e283f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model import android.telephony.Annotation +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel /** * Model for demo wifi commands, ported from [NetworkControllerImpl] @@ -29,6 +30,8 @@ sealed interface FakeWifiEventModel { @Annotation.DataActivityType val activity: Int, val ssid: String?, val validated: Boolean?, + val hotspotDeviceType: WifiNetworkModel.HotspotDeviceType = + WifiNetworkModel.HotspotDeviceType.NONE, ) : FakeWifiEventModel data class CarrierMerged( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 9944c00c6ee7e..946c571998ac8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -261,6 +261,8 @@ constructor( isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED), level = wifiManager.calculateSignalLevel(wifiInfo.rssi), wifiInfo.ssid, + // This repository doesn't support any hotspot information. + WifiNetworkModel.HotspotDeviceType.NONE, wifiInfo.isPasspointAp, wifiInfo.isOsuAp, wifiInfo.passpointProviderFriendlyName diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index 6f1ac34de9d36..43f8bc6ef3f8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -23,6 +23,8 @@ import androidx.lifecycle.LifecycleRegistry import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.table.TableLogBuffer @@ -37,6 +39,8 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiReposito import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType +import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry import com.android.wifitrackerlib.WifiPickerTracker @@ -61,6 +65,7 @@ import kotlinx.coroutines.flow.stateIn class WifiRepositoryViaTrackerLib @Inject constructor( + featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @@ -74,6 +79,8 @@ constructor( mainExecutor.execute { it.currentState = Lifecycle.State.CREATED } } + private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER) + private var wifiPickerTracker: WifiPickerTracker? = null private val wifiPickerTrackerInfo: StateFlow = run { @@ -181,11 +188,19 @@ constructor( numberOfLevels = wifiManager.maxSignalLevel + 1, ) } else { + val hotspotDeviceType = + if (isInstantTetherEnabled && this is HotspotNetworkEntry) { + this.deviceType.toHotspotDeviceType() + } else { + WifiNetworkModel.HotspotDeviceType.NONE + } + WifiNetworkModel.Active( networkId = NETWORK_ID, isValidated = this.hasInternetAccess(), level = this.level, ssid = this.title, + hotspotDeviceType = hotspotDeviceType, // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can // always be false/null in this repository. @@ -259,6 +274,7 @@ constructor( class Factory @Inject constructor( + private val featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @@ -267,6 +283,7 @@ constructor( ) { fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib { return WifiRepositoryViaTrackerLib( + featureFlags, scope, mainExecutor, wifiPickerTrackerFactory, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index 4b33c88cea303..c11640a31b82c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -17,11 +17,13 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model import android.net.wifi.WifiManager.UNKNOWN_SSID +import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo import android.telephony.SubscriptionManager import androidx.annotation.VisibleForTesting import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType /** Provides information about the current wifi network. */ sealed class WifiNetworkModel : Diffable { @@ -52,6 +54,7 @@ sealed class WifiNetworkModel : Diffable { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -83,6 +86,7 @@ sealed class WifiNetworkModel : Diffable { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -110,6 +114,7 @@ sealed class WifiNetworkModel : Diffable { row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -184,6 +189,7 @@ sealed class WifiNetworkModel : Diffable { row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, numberOfLevels) row.logChange(COL_SSID, null) + row.logChange(COL_HOTSPOT, null) row.logChange(COL_PASSPOINT_ACCESS_POINT, false) row.logChange(COL_ONLINE_SIGN_UP, false) row.logChange(COL_PASSPOINT_NAME, null) @@ -209,6 +215,12 @@ sealed class WifiNetworkModel : Diffable { /** See [android.net.wifi.WifiInfo.ssid]. */ val ssid: String? = null, + /** + * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this + * isn't a hotspot connection. + */ + val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, + /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ val isPasspointAccessPoint: Boolean = false, @@ -247,6 +259,9 @@ sealed class WifiNetworkModel : Diffable { if (prevVal.ssid != ssid) { row.logChange(COL_SSID, ssid) } + if (prevVal.hotspotDeviceType != hotspotDeviceType) { + row.logChange(COL_HOTSPOT, hotspotDeviceType.name) + } // TODO(b/238425913): The passpoint-related values are frequently never used, so it // would be great to not log them when they're not used. @@ -272,6 +287,7 @@ sealed class WifiNetworkModel : Diffable { row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, null) row.logChange(COL_SSID, ssid) + row.logChange(COL_HOTSPOT, hotspotDeviceType.name) row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) @@ -305,6 +321,40 @@ sealed class WifiNetworkModel : Diffable { companion object { @VisibleForTesting internal const val MIN_VALID_LEVEL = 0 } + + /** + * Enum for the type of device providing the hotspot connection, or [NONE] if this connection + * isn't a hotspot. + */ + enum class HotspotDeviceType { + /* This wifi connection isn't a hotspot. */ + NONE, + /** The device type for this hotspot is unknown. */ + UNKNOWN, + PHONE, + TABLET, + LAPTOP, + WATCH, + AUTO, + /** The device type sent for this hotspot is invalid to SysUI. */ + INVALID, + } + + /** + * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to + * our internal representation. + */ + fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType { + return when (this) { + NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN + NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE + NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET + NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP + NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH + NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO + else -> HotspotDeviceType.INVALID + } + } } const val TYPE_CARRIER_MERGED = "CarrierMerged" @@ -319,6 +369,7 @@ const val COL_VALIDATED = "isValidated" const val COL_LEVEL = "level" const val COL_NUM_LEVELS = "maxLevel" const val COL_SSID = "ssid" +const val COL_HOTSPOT = "hotspot" const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index e7d420bcb32bc..9016220b40cfb 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -419,7 +419,15 @@ public class CarrierTextManagerTest extends SysuiTestCase { assertFalse(mWifiRepository.isWifiConnectedWithValidSsid()); mWifiRepository.setWifiNetwork( - new WifiNetworkModel.Active(0, false, 0, "", false, false, null)); + new WifiNetworkModel.Active( + /* networkId= */ 0, + /* isValidated= */ false, + /* level= */ 0, + /* ssid= */ "", + /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE, + /* isPasspointAccessPoint= */ false, + /* isOnlineSignUpForPasspointAccessPoint= */ false, + /* passpointProviderFriendlyName= */ null)); assertTrue(mWifiRepository.isWifiConnectedWithValidSsid()); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index fef042be65a82..2dbeb7aa7e906 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -488,6 +488,26 @@ class WifiRepositoryImplTest : SysuiTestCase() { assertThat(latestActive.ssid).isEqualTo(SSID) } + @Test + fun wifiNetwork_neverHasHotspot() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiInfo = + mock().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } + + getNetworkCallback() + .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(latest is WifiNetworkModel.Active).isTrue() + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) + } + @Test fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index bd20e415ab371..7a78ae368fa9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -18,10 +18,13 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.net.wifi.WifiManager import android.net.wifi.WifiManager.UNKNOWN_SSID +import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory @@ -35,6 +38,8 @@ import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.android.wifitrackerlib.HotspotNetworkEntry +import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry import com.android.wifitrackerlib.WifiPickerTracker @@ -59,10 +64,25 @@ import org.mockito.Mockito.verify @TestableLooper.RunWithLooper(setAsMainLooper = true) class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { - private lateinit var underTest: WifiRepositoryViaTrackerLib + // Using lazy means that the class will only be constructed once it's fetched. Because the + // repository internally sets some values on construction, we need to set up some test + // parameters (like feature flags) *before* construction. Using lazy allows us to do that setup + // inside each test case without needing to manually recreate the repository. + private val underTest: WifiRepositoryViaTrackerLib by lazy { + WifiRepositoryViaTrackerLib( + featureFlags, + testScope.backgroundScope, + executor, + wifiPickerTrackerFactory, + wifiManager, + logger, + tableLogger, + ) + } private val executor = FakeExecutor(FakeSystemClock()) private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock()) + private val featureFlags = FakeFeatureFlags() private val tableLogger = mock() private val wifiManager = mock().apply { whenever(this.maxSignalLevel).thenReturn(10) } @@ -76,9 +96,9 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { @Before fun setUp() { + featureFlags.set(Flags.INSTANT_TETHER, false) whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor))) .thenReturn(wifiPickerTracker) - underTest = createRepo() } @Test @@ -272,6 +292,136 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latestActive.passpointProviderFriendlyName).isNull() } + @Test + fun wifiNetwork_notHotspot_none() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) + } + + @Test + fun wifiNetwork_hotspot_unknown() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.UNKNOWN) + } + + @Test + fun wifiNetwork_hotspot_phone() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_PHONE) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.PHONE) + } + + @Test + fun wifiNetwork_hotspot_tablet() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_TABLET) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.TABLET) + } + + @Test + fun wifiNetwork_hotspot_laptop() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_LAPTOP) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.LAPTOP) + } + + @Test + fun wifiNetwork_hotspot_watch() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.WATCH) + } + + @Test + fun wifiNetwork_hotspot_auto() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_AUTO) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.AUTO) + } + + @Test + fun wifiNetwork_hotspot_invalid() = + testScope.runTest { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(1234) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.INVALID) + } + + @Test + fun wifiNetwork_hotspot_flagOff_valueNotUsed() = + testScope.runTest { + // WHEN the flag is off + featureFlags.set(Flags.INSTANT_TETHER, false) + + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH) + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + // THEN NONE is always used, even if the wifi entry does have a hotspot device type + assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType) + .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE) + } + @Test fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = testScope.runTest { @@ -726,15 +876,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { return callbackCaptor.value!! } - private fun createRepo(): WifiRepositoryViaTrackerLib { - return WifiRepositoryViaTrackerLib( - testScope.backgroundScope, - executor, - wifiPickerTrackerFactory, - wifiManager, - logger, - tableLogger, - ) + private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry { + return mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.deviceType).thenReturn(type) + } } private companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index 4e0c309512e87..ba035bec340c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -136,7 +136,8 @@ class WifiNetworkModelTest : SysuiTestCase() { networkId = 5, isValidated = true, level = 3, - ssid = "Test SSID" + ssid = "Test SSID", + hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP, ) activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) @@ -146,6 +147,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "LAPTOP")) } @Test fun logDiffs_activeToInactive_resetsAllActiveFields() { @@ -165,6 +167,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "null")) } @Test @@ -175,7 +178,8 @@ class WifiNetworkModelTest : SysuiTestCase() { networkId = 5, isValidated = true, level = 3, - ssid = "Test SSID" + ssid = "Test SSID", + hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO, ) val prevVal = WifiNetworkModel.CarrierMerged( @@ -191,6 +195,7 @@ class WifiNetworkModelTest : SysuiTestCase() { assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) assertThat(logger.changes).contains(Pair(COL_LEVEL, "3")) assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID")) + assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "AUTO")) } @Test fun logDiffs_activeToCarrierMerged_logsAllFields() { From d658196553fab0c88ecdd740fecc517608911cfe Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Fri, 4 Aug 2023 13:59:24 +0000 Subject: [PATCH 4/6] [Status Bar][Wifi] Add carrier merged sub ID to TrackerLibRepo. Bug: 292534484 Test: atest WifiRepositoryViaTrackerLibTest Change-Id: Idb0fc71f82eb0bbe0309f86dd78d46efe862e46a --- .../wifi/data/repository/WifiRepository.kt | 3 + .../repository/prod/WifiRepositoryImpl.kt | 4 +- .../prod/WifiRepositoryViaTrackerLib.kt | 71 ++++++++++--------- .../prod/WifiRepositoryViaTrackerLibTest.kt | 57 ++++++--------- 4 files changed, 64 insertions(+), 71 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index b11b4727c3c3b..b29d46174bd17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -49,6 +49,9 @@ interface WifiRepository { const val COL_NAME_IS_ENABLED = "isEnabled" /** Column name to use for [isWifiDefault] for table logging. */ const val COL_NAME_IS_DEFAULT = "isDefault" + + const val CARRIER_MERGED_INVALID_SUB_ID_REASON = + "Wifi network was carrier merged but had invalid sub ID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index 946c571998ac8..afd15765d163d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger @@ -277,9 +278,6 @@ constructor( .addTransportType(TRANSPORT_WIFI) .addTransportType(TRANSPORT_CELLULAR) .build() - - private const val CARRIER_MERGED_INVALID_SUB_ID_REASON = - "Wifi network was carrier merged but had invalid sub ID" } @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index 43f8bc6ef3f8f..7cf9a7d5a481b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.net.wifi.WifiManager +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -33,6 +34,7 @@ import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger @@ -178,41 +180,52 @@ constructor( return WIFI_NETWORK_DEFAULT } return if (this is MergedCarrierEntry) { + this.convertCarrierMergedToModel() + } else { + this.convertNormalToModel() + } + } + + private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel { + return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) { + WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON) + } else { WifiNetworkModel.CarrierMerged( networkId = NETWORK_ID, - // TODO(b/292534484): Fetch the real subscription ID from [MergedCarrierEntry]. - subscriptionId = TEMP_SUB_ID, + subscriptionId = this.subscriptionId, level = this.level, // WifiManager APIs to calculate the signal level start from 0, so // maxSignalLevel + 1 represents the total level buckets count. numberOfLevels = wifiManager.maxSignalLevel + 1, ) - } else { - val hotspotDeviceType = - if (isInstantTetherEnabled && this is HotspotNetworkEntry) { - this.deviceType.toHotspotDeviceType() - } else { - WifiNetworkModel.HotspotDeviceType.NONE - } - - WifiNetworkModel.Active( - networkId = NETWORK_ID, - isValidated = this.hasInternetAccess(), - level = this.level, - ssid = this.title, - hotspotDeviceType = hotspotDeviceType, - // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for - // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can - // always be false/null in this repository. - // TODO(b/292534484): Remove these fields from the wifi network model once this - // repository is fully enabled. - isPasspointAccessPoint = false, - isOnlineSignUpForPasspointAccessPoint = false, - passpointProviderFriendlyName = null, - ) } } + private fun WifiEntry.convertNormalToModel(): WifiNetworkModel { + val hotspotDeviceType = + if (isInstantTetherEnabled && this is HotspotNetworkEntry) { + this.deviceType.toHotspotDeviceType() + } else { + WifiNetworkModel.HotspotDeviceType.NONE + } + + return WifiNetworkModel.Active( + networkId = NETWORK_ID, + isValidated = this.hasInternetAccess(), + level = this.level, + ssid = this.title, + hotspotDeviceType = hotspotDeviceType, + // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for + // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can + // always be false/null in this repository. + // TODO(b/292534484): Remove these fields from the wifi network model once this + // repository is fully enabled. + isPasspointAccessPoint = false, + isOnlineSignUpForPasspointAccessPoint = false, + passpointProviderFriendlyName = null, + ) + } + override val isWifiDefault: StateFlow = wifiPickerTrackerInfo .map { it.isDefault } @@ -310,13 +323,5 @@ constructor( * to [WifiRepositoryViaTrackerLib]. */ private const val NETWORK_ID = -1 - - /** - * A temporary subscription ID until WifiTrackerLib exposes a method to fetch the - * subscription ID. - * - * Use -2 because [SubscriptionManager.INVALID_SUBSCRIPTION_ID] is -1. - */ - private const val TEMP_SUB_ID = -2 } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 7a78ae368fa9e..6d2954b0e8565 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.net.wifi.WifiManager import android.net.wifi.WifiManager.UNKNOWN_SSID import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -431,6 +432,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) + whenever(this.subscriptionId).thenReturn(567) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -438,7 +440,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() val latestMerged = latest as WifiNetworkModel.CarrierMerged assertThat(latestMerged.level).isEqualTo(3) - // numberOfLevels = maxSignalLevel + 1 + assertThat(latestMerged.subscriptionId).isEqualTo(567) } @Test @@ -461,30 +463,23 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latestMerged.numberOfLevels).isEqualTo(6) } - /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. @Test fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) - val wifiInfo = - mock().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) - getNetworkCallback() - .onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) + getCallback().onWifiEntriesChanged() assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java) } - */ - @Test fun wifiNetwork_notValidated_networkNotValidated() = testScope.runTest { @@ -714,30 +709,22 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() } - /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID. - @Test - fun isWifiConnectedWithValidSsid_invalidNetwork_false() = - testScope.runTest { - collectLastValue(underTest.wifiNetwork) + @Test + fun isWifiConnectedWithValidSsid_invalidNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) - val wifiInfo = - mock().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) - whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) - } + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() - getNetworkCallback() - .onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) - testScope.runCurrent() - - assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() - } - - */ + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } @Test fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() = From c13426333419a80198080825894f27000839eb3d Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Fri, 4 Aug 2023 14:48:32 +0000 Subject: [PATCH 5/6] [Status Bar][Wifi] Disable wifi scanning in TrackerLibRepo. Bug: 292534484 Test: atest WifiRepositoryViaTrackerLibTest Change-Id: Ifea61ce54fbc92b2b990b6cd86ab712aa5693f91 --- .../prod/WifiRepositoryViaTrackerLib.kt | 22 ++++++++++--------- .../prod/WifiRepositoryViaTrackerLibTest.kt | 9 ++++++++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index 7cf9a7d5a481b..604fc870af585 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -136,19 +136,21 @@ constructor( } } - // TODO(b/292591403): [WifiPickerTrackerFactory] currently scans to see all - // available wifi networks every 10s. Because SysUI only needs to display the - // **connected** network, we don't need scans to be running. We should disable these - // scans (ideal) or at least run them very infrequently. - wifiPickerTracker = wifiPickerTrackerFactory.create(lifecycle, callback) + wifiPickerTracker = + wifiPickerTrackerFactory.create(lifecycle, callback).apply { + // By default, [WifiPickerTracker] will scan to see all available wifi + // networks in the area. Because SysUI only needs to display the + // **connected** network, we don't need scans to be running (and in fact, + // running scans is costly and should be avoided whenever possible). + this?.disableScanning() + } // The lifecycle must be STARTED in order for the callback to receive events. mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED } awaitClose { mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED } } } - // TODO(b/292534484): Update to Eagerly once scans are disabled. (Here and other flows) - .stateIn(scope, SharingStarted.WhileSubscribed(), current) + .stateIn(scope, SharingStarted.Eagerly, current) } override val isWifiEnabled: StateFlow = @@ -161,7 +163,7 @@ constructor( columnName = COL_NAME_IS_ENABLED, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) override val wifiNetwork: StateFlow = wifiPickerTrackerInfo @@ -172,7 +174,7 @@ constructor( columnPrefix = "", initialValue = WIFI_NETWORK_DEFAULT, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), WIFI_NETWORK_DEFAULT) + .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT) /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */ private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel { @@ -236,7 +238,7 @@ constructor( columnName = COL_NAME_IS_DEFAULT, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) override val wifiActivity: StateFlow = WifiRepositoryHelper.createActivityFlow( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 6d2954b0e8565..27975f5307414 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -102,6 +102,15 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { .thenReturn(wifiPickerTracker) } + @Test + fun wifiPickerTrackerCreation_scansDisabled() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + testScope.runCurrent() + + verify(wifiPickerTracker).disableScanning() + } + @Test fun isWifiEnabled_enabled_true() = testScope.runTest { From bdd10e556f19fcc3ec7ba630311f0b55946cc841 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Fri, 4 Aug 2023 15:25:52 +0000 Subject: [PATCH 6/6] [Status Bar][Wifi] Protect against invalid levels from WifiTrackerLib. Fixes: 294544380 Bug: 292534484 Test: flip flag on, turn wifi off then on again -> verify no crash Test: atest WifiRepositoryViaTrackerLibTest Change-Id: I040f8c30761c7510e54866a6f849f5bde30a2ed4 --- .../prod/WifiRepositoryViaTrackerLib.kt | 9 ++ .../wifi/shared/model/WifiNetworkModel.kt | 4 + .../prod/WifiRepositoryViaTrackerLibTest.kt | 85 +++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index 604fc870af585..175563bb07640 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -45,6 +45,9 @@ import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkMode import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import java.util.concurrent.Executor import javax.inject.Inject @@ -204,6 +207,12 @@ constructor( } private fun WifiEntry.convertNormalToModel(): WifiNetworkModel { + if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) { + // If our level means the network is unreachable or the level is otherwise invalid, we + // don't have an active network. + return WifiNetworkModel.Inactive + } + val hotspotDeviceType = if (isInstantTetherEnabled && this is HotspotNetworkEntry) { this.deviceType.toHotspotDeviceType() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index c11640a31b82c..7078a2e1728c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -314,11 +314,15 @@ sealed class WifiNetworkModel : Diffable { } companion object { + // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead + // once the migration to WifiTrackerLib is complete. @VisibleForTesting internal const val MAX_VALID_LEVEL = 4 } } companion object { + // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead + // once the migration to WifiTrackerLib is complete. @VisibleForTesting internal const val MIN_VALID_LEVEL = 0 } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index 27975f5307414..9959e00fd3f37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -43,6 +43,9 @@ import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -302,6 +305,88 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { assertThat(latestActive.passpointProviderFriendlyName).isNull() } + @Test + fun wifiNetwork_unreachableLevel_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelTooHigh_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MAX + 1) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelTooLow_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MIN - 1) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + } + + @Test + fun wifiNetwork_levelIsMax_activeNetworkWithMaxLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MAX) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java) + assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MAX) + } + + @Test + fun wifiNetwork_levelIsMin_activeNetworkWithMinLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_MIN) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + + assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java) + assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MIN) + } + @Test fun wifiNetwork_notHotspot_none() = testScope.runTest {