diff options
14 files changed, 656 insertions, 139 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 3696b781425e..66f66cc8ff3b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -370,6 +370,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/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index b11b4727c3c3..b29d46174bd1 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/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt index 7d2501ca0e79..ab9b516b837f 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 a57be665f105..99b680056d7f 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 f5035cbc0215..b2e843e283f7 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/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt new file mode 100644 index 000000000000..f1b98b3972e1 --- /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<DataActivityModel> { + 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 995de6d2fc61..afd15765d163 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,10 +39,10 @@ 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 +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 @@ -218,29 +217,15 @@ constructor( ) override val wifiActivity: StateFlow<DataActivityModel> = - 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 @@ -277,6 +262,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 @@ -284,16 +271,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() @@ -301,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 127136789ba6..175563bb0764 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,12 +17,15 @@ 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 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 @@ -31,20 +34,25 @@ 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 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.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 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 @@ -62,6 +70,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, @@ -75,6 +84,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<WifiPickerTrackerInfo> = run { @@ -128,19 +139,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<Boolean> = @@ -153,7 +166,7 @@ constructor( columnName = COL_NAME_IS_ENABLED, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) override val wifiNetwork: StateFlow<WifiNetworkModel> = wifiPickerTrackerInfo @@ -164,7 +177,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 { @@ -172,30 +185,58 @@ 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 { - WifiNetworkModel.Active( - 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). - isPasspointAccessPoint = false, - isOnlineSignUpForPasspointAccessPoint = false, - passpointProviderFriendlyName = null, - ) } } + 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() + } 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<Boolean> = wifiPickerTrackerInfo .map { it.isDefault } @@ -206,12 +247,16 @@ constructor( columnName = COL_NAME_IS_DEFAULT, initialValue = false, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) - // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since - // WifiTrackerLib doesn't expose activity details. override val wifiActivity: StateFlow<DataActivityModel> = - MutableStateFlow(DataActivityModel(false, false)) + WifiRepositoryHelper.createActivityFlow( + wifiManager, + mainExecutor, + scope, + wifiTrackerLibTableLogBuffer, + this::logActivity, + ) private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) { inputLogger.log( @@ -231,6 +276,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]. * @@ -249,6 +298,7 @@ constructor( class Factory @Inject constructor( + private val featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val wifiPickerTrackerFactory: WifiPickerTrackerFactory, @@ -257,6 +307,7 @@ constructor( ) { fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib { return WifiRepositoryViaTrackerLib( + featureFlags, scope, mainExecutor, wifiPickerTrackerFactory, @@ -283,13 +334,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/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 4b33c88cea30..7078a2e1728c 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<WifiNetworkModel> { @@ -52,6 +54,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { 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<WifiNetworkModel> { 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<WifiNetworkModel> { 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<WifiNetworkModel> { 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<WifiNetworkModel> { /** 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<WifiNetworkModel> { 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<WifiNetworkModel> { 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) @@ -298,13 +314,51 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } 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 } + + /** + * 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 +373,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 e7d420bcb32b..9016220b40cf 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/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index 1bf431b4ea13..1c8dac14b089 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/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index fef042be65a8..2dbeb7aa7e90 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 @@ -489,6 +489,26 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_neverHasHotspot() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock<Network>().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 { val latest by collectLastValue(underTest.wifiNetwork) 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 7002cbb6ab21..9959e00fd3f3 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,13 +18,18 @@ 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 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 +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 @@ -34,8 +39,13 @@ 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.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 @@ -45,6 +55,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. @@ -57,10 +68,25 @@ import org.junit.Test @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<TableLogBuffer>() private val wifiManager = mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) } @@ -74,12 +100,21 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { @Before fun setUp() { + featureFlags.set(Flags.INSTANT_TETHER, false) whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor))) .thenReturn(wifiPickerTracker) - underTest = createRepo() } @Test + fun wifiPickerTrackerCreation_scansDisabled() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) + testScope.runCurrent() + + verify(wifiPickerTracker).disableScanning() + } + + @Test fun isWifiEnabled_enabled_true() = testScope.runTest { val latest by collectLastValue(underTest.isWifiEnabled) @@ -238,7 +273,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().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() @@ -246,7 +281,240 @@ 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<WifiEntry>().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 + fun wifiNetwork_unreachableLevel_inactiveNetwork() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().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<WifiEntry>().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<WifiEntry>().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<WifiEntry>().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<WifiEntry>().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 { + featureFlags.set(Flags.INSTANT_TETHER, true) + val latest by collectLastValue(underTest.wifiNetwork) + + val wifiEntry = + mock<WifiEntry>().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 @@ -258,6 +526,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<MergedCarrierEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) whenever(this.level).thenReturn(3) + whenever(this.subscriptionId).thenReturn(567) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -265,7 +534,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 @@ -288,30 +557,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<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) + val wifiEntry = + mock<MergedCarrierEntry>().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 { @@ -382,7 +644,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().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() @@ -397,7 +659,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().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() @@ -430,12 +692,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock<WifiEntry>().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) @@ -480,7 +742,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { mock<WifiEntry>().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() @@ -488,7 +750,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) @@ -497,7 +759,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 @@ -541,40 +803,32 @@ 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) - - val wifiInfo = - mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) - whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) - } - - getNetworkCallback() - .onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) - testScope.runCurrent() + @Test + fun isWifiConnectedWithValidSsid_invalidNetwork_false() = + testScope.runTest { + collectLastValue(underTest.wifiNetwork) - assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() - } + val wifiEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + getCallback().onWifiEntriesChanged() + testScope.runCurrent() - */ + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn(null) + whenever(this.title).thenReturn(null) } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -584,14 +838,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() = + fun isWifiConnectedWithValidSsid_activeNetwork_unknownTitle_false() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().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() @@ -601,14 +855,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { } @Test - fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() = + fun isWifiConnectedWithValidSsid_activeNetwork_validTitle_true() = testScope.runTest { collectLastValue(underTest.wifiNetwork) val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -626,7 +880,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() { val wifiEntry = mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) - whenever(this.ssid).thenReturn("fakeSsid") + whenever(this.title).thenReturn("fakeSsid") } whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() @@ -643,23 +897,74 @@ 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 createRepo(): WifiRepositoryViaTrackerLib { - return WifiRepositoryViaTrackerLib( - testScope.backgroundScope, - executor, - wifiPickerTrackerFactory, - wifiManager, - logger, - tableLogger, - ) + private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback { + testScope.runCurrent() + val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>() + verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture()) + return callbackCaptor.value!! + } + + private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry { + return mock<HotspotNetworkEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.deviceType).thenReturn(type) + } } private companion object { - const val SSID = "AB" + const val TITLE = "AB" } } 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 4e0c309512e8..ba035bec340c 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() { |