1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.pipeline.wifi.shared.model 18 19 import android.net.wifi.WifiManager.UNKNOWN_SSID 20 import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo 21 import android.telephony.SubscriptionManager 22 import androidx.annotation.VisibleForTesting 23 import com.android.systemui.log.table.Diffable 24 import com.android.systemui.log.table.TableRowLogger 25 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository 26 import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType 27 28 /** Provides information about the current wifi network. */ 29 sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { 30 31 // TODO(b/238425913): Have a better, more unified strategy for diff-logging instead of 32 // copy-pasting the column names for each sub-object. 33 34 /** 35 * A model representing that we couldn't fetch any wifi information. 36 * 37 * This is only used with [DisabledWifiRepository], where [WifiManager] is null. 38 */ 39 object Unavailable : WifiNetworkModel() { toStringnull40 override fun toString() = "WifiNetwork.Unavailable" 41 override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { 42 if (prevVal is Unavailable) { 43 return 44 } 45 46 logFull(row) 47 } 48 logFullnull49 override fun logFull(row: TableRowLogger) { 50 row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE) 51 row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) 52 row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) 53 row.logChange(COL_VALIDATED, false) 54 row.logChange(COL_LEVEL, LEVEL_DEFAULT) 55 row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) 56 row.logChange(COL_SSID, null) 57 row.logChange(COL_HOTSPOT, null) 58 row.logChange(COL_PASSPOINT_ACCESS_POINT, false) 59 row.logChange(COL_ONLINE_SIGN_UP, false) 60 row.logChange(COL_PASSPOINT_NAME, null) 61 } 62 } 63 64 /** A model representing that the wifi information we received was invalid in some way. */ 65 data class Invalid( 66 /** A description of why the wifi information was invalid. */ 67 val invalidReason: String, 68 ) : WifiNetworkModel() { toStringnull69 override fun toString() = "WifiNetwork.Invalid[$invalidReason]" 70 override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { 71 if (prevVal !is Invalid) { 72 logFull(row) 73 return 74 } 75 76 if (invalidReason != prevVal.invalidReason) { 77 row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason") 78 } 79 } 80 logFullnull81 override fun logFull(row: TableRowLogger) { 82 row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason") 83 row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) 84 row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) 85 row.logChange(COL_VALIDATED, false) 86 row.logChange(COL_LEVEL, LEVEL_DEFAULT) 87 row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) 88 row.logChange(COL_SSID, null) 89 row.logChange(COL_HOTSPOT, null) 90 row.logChange(COL_PASSPOINT_ACCESS_POINT, false) 91 row.logChange(COL_ONLINE_SIGN_UP, false) 92 row.logChange(COL_PASSPOINT_NAME, null) 93 } 94 } 95 96 /** A model representing that we have no active wifi network. */ 97 object Inactive : WifiNetworkModel() { toStringnull98 override fun toString() = "WifiNetwork.Inactive" 99 100 override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { 101 if (prevVal is Inactive) { 102 return 103 } 104 105 // When changing to Inactive, we need to log diffs to all the fields. 106 logFull(row) 107 } 108 logFullnull109 override fun logFull(row: TableRowLogger) { 110 row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) 111 row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) 112 row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) 113 row.logChange(COL_VALIDATED, false) 114 row.logChange(COL_LEVEL, LEVEL_DEFAULT) 115 row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT) 116 row.logChange(COL_SSID, null) 117 row.logChange(COL_HOTSPOT, null) 118 row.logChange(COL_PASSPOINT_ACCESS_POINT, false) 119 row.logChange(COL_ONLINE_SIGN_UP, false) 120 row.logChange(COL_PASSPOINT_NAME, null) 121 } 122 } 123 124 /** 125 * A model representing that our wifi network is actually a carrier merged network, meaning it's 126 * treated as more of a mobile network. 127 * 128 * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information. 129 */ 130 data class CarrierMerged( 131 /** 132 * The [android.net.Network.netId] we received from 133 * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. 134 * 135 * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. 136 */ 137 val networkId: Int, 138 139 /** 140 * The subscription ID that this connection represents. 141 * 142 * Comes from [android.net.wifi.WifiInfo.getSubscriptionId]. 143 * 144 * Per that method, this value must not be [INVALID_SUBSCRIPTION_ID] (if it was invalid, 145 * then this is *not* a carrier merged network). 146 */ 147 val subscriptionId: Int, 148 149 /** The signal level, guaranteed to be 0 <= level <= numberOfLevels. */ 150 val level: Int, 151 152 /** The maximum possible level. */ 153 val numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS, 154 ) : WifiNetworkModel() { 155 init { <lambda>null156 require(level in MIN_VALID_LEVEL..numberOfLevels) { 157 "0 <= wifi level <= $numberOfLevels required; level was $level" 158 } <lambda>null159 require(subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 160 "subscription ID cannot be invalid" 161 } 162 } 163 logDiffsnull164 override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { 165 if (prevVal !is CarrierMerged) { 166 logFull(row) 167 return 168 } 169 170 if (prevVal.networkId != networkId) { 171 row.logChange(COL_NETWORK_ID, networkId) 172 } 173 if (prevVal.subscriptionId != subscriptionId) { 174 row.logChange(COL_SUB_ID, subscriptionId) 175 } 176 if (prevVal.level != level) { 177 row.logChange(COL_LEVEL, level) 178 } 179 if (prevVal.numberOfLevels != numberOfLevels) { 180 row.logChange(COL_NUM_LEVELS, numberOfLevels) 181 } 182 } 183 logFullnull184 override fun logFull(row: TableRowLogger) { 185 row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) 186 row.logChange(COL_NETWORK_ID, networkId) 187 row.logChange(COL_SUB_ID, subscriptionId) 188 row.logChange(COL_VALIDATED, true) 189 row.logChange(COL_LEVEL, level) 190 row.logChange(COL_NUM_LEVELS, numberOfLevels) 191 row.logChange(COL_SSID, null) 192 row.logChange(COL_HOTSPOT, null) 193 row.logChange(COL_PASSPOINT_ACCESS_POINT, false) 194 row.logChange(COL_ONLINE_SIGN_UP, false) 195 row.logChange(COL_PASSPOINT_NAME, null) 196 } 197 } 198 199 /** Provides information about an active wifi network. */ 200 data class Active( 201 /** 202 * The [android.net.Network.netId] we received from 203 * [android.net.ConnectivityManager.NetworkCallback] in association with this wifi network. 204 * 205 * Importantly, **not** [android.net.wifi.WifiInfo.getNetworkId]. 206 */ 207 val networkId: Int, 208 209 /** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */ 210 val isValidated: Boolean = false, 211 212 /** The wifi signal level, guaranteed to be 0 <= level <= 4. */ 213 val level: Int, 214 215 /** See [android.net.wifi.WifiInfo.ssid]. */ 216 val ssid: String? = null, 217 218 /** 219 * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this 220 * isn't a hotspot connection. 221 */ 222 val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, 223 224 /** See [android.net.wifi.WifiInfo.isPasspointAp]. */ 225 val isPasspointAccessPoint: Boolean = false, 226 227 /** See [android.net.wifi.WifiInfo.isOsuAp]. */ 228 val isOnlineSignUpForPasspointAccessPoint: Boolean = false, 229 230 /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */ 231 val passpointProviderFriendlyName: String? = null, 232 ) : WifiNetworkModel() { 233 init { <lambda>null234 require(level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) { 235 "0 <= wifi level <= 4 required; level was $level" 236 } 237 } 238 239 /** Returns true if this network has a valid SSID and false otherwise. */ hasValidSsidnull240 fun hasValidSsid(): Boolean { 241 return ssid != null && ssid != UNKNOWN_SSID 242 } 243 logDiffsnull244 override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { 245 if (prevVal !is Active) { 246 logFull(row) 247 return 248 } 249 250 if (prevVal.networkId != networkId) { 251 row.logChange(COL_NETWORK_ID, networkId) 252 } 253 if (prevVal.isValidated != isValidated) { 254 row.logChange(COL_VALIDATED, isValidated) 255 } 256 if (prevVal.level != level) { 257 row.logChange(COL_LEVEL, level) 258 } 259 if (prevVal.ssid != ssid) { 260 row.logChange(COL_SSID, ssid) 261 } 262 if (prevVal.hotspotDeviceType != hotspotDeviceType) { 263 row.logChange(COL_HOTSPOT, hotspotDeviceType.name) 264 } 265 266 // TODO(b/238425913): The passpoint-related values are frequently never used, so it 267 // would be great to not log them when they're not used. 268 if (prevVal.isPasspointAccessPoint != isPasspointAccessPoint) { 269 row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) 270 } 271 if ( 272 prevVal.isOnlineSignUpForPasspointAccessPoint != 273 isOnlineSignUpForPasspointAccessPoint 274 ) { 275 row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) 276 } 277 if (prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) { 278 row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) 279 } 280 } 281 logFullnull282 override fun logFull(row: TableRowLogger) { 283 row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE) 284 row.logChange(COL_NETWORK_ID, networkId) 285 row.logChange(COL_SUB_ID, null) 286 row.logChange(COL_VALIDATED, isValidated) 287 row.logChange(COL_LEVEL, level) 288 row.logChange(COL_NUM_LEVELS, null) 289 row.logChange(COL_SSID, ssid) 290 row.logChange(COL_HOTSPOT, hotspotDeviceType.name) 291 row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) 292 row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) 293 row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName) 294 } 295 toStringnull296 override fun toString(): String { 297 // Only include the passpoint-related values in the string if we have them. (Most 298 // networks won't have them so they'll be mostly clutter.) 299 val passpointString = 300 if ( 301 isPasspointAccessPoint || 302 isOnlineSignUpForPasspointAccessPoint || 303 passpointProviderFriendlyName != null 304 ) { 305 ", isPasspointAp=$isPasspointAccessPoint, " + 306 "isOnlineSignUpForPasspointAp=$isOnlineSignUpForPasspointAccessPoint, " + 307 "passpointName=$passpointProviderFriendlyName" 308 } else { 309 "" 310 } 311 312 return "WifiNetworkModel.Active(networkId=$networkId, isValidated=$isValidated, " + 313 "level=$level, ssid=$ssid$passpointString)" 314 } 315 316 companion object { 317 // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead 318 // once the migration to WifiTrackerLib is complete. 319 @VisibleForTesting internal const val MAX_VALID_LEVEL = 4 320 } 321 } 322 323 companion object { 324 // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead 325 // once the migration to WifiTrackerLib is complete. 326 @VisibleForTesting internal const val MIN_VALID_LEVEL = 0 327 } 328 329 /** 330 * Enum for the type of device providing the hotspot connection, or [NONE] if this connection 331 * isn't a hotspot. 332 */ 333 enum class HotspotDeviceType { 334 /* This wifi connection isn't a hotspot. */ 335 NONE, 336 /** The device type for this hotspot is unknown. */ 337 UNKNOWN, 338 PHONE, 339 TABLET, 340 LAPTOP, 341 WATCH, 342 AUTO, 343 /** The device type sent for this hotspot is invalid to SysUI. */ 344 INVALID, 345 } 346 347 /** 348 * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to 349 * our internal representation. 350 */ toHotspotDeviceTypenull351 fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType { 352 return when (this) { 353 NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN 354 NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE 355 NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET 356 NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP 357 NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH 358 NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO 359 else -> HotspotDeviceType.INVALID 360 } 361 } 362 } 363 364 const val TYPE_CARRIER_MERGED = "CarrierMerged" 365 const val TYPE_UNAVAILABLE = "Unavailable" 366 const val TYPE_INACTIVE = "Inactive" 367 const val TYPE_ACTIVE = "Active" 368 369 const val COL_NETWORK_TYPE = "type" 370 const val COL_NETWORK_ID = "networkId" 371 const val COL_SUB_ID = "subscriptionId" 372 const val COL_VALIDATED = "isValidated" 373 const val COL_LEVEL = "level" 374 const val COL_NUM_LEVELS = "maxLevel" 375 const val COL_SSID = "ssid" 376 const val COL_HOTSPOT = "hotspot" 377 const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" 378 const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" 379 const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" 380 381 val LEVEL_DEFAULT: String? = null 382 val NUM_LEVELS_DEFAULT: String? = null 383 val NETWORK_ID_DEFAULT: String? = null 384 val SUB_ID_DEFAULT: String? = null 385