• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
20 import android.net.wifi.WifiManager.UNKNOWN_SSID
21 import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
22 import android.telephony.SubscriptionManager
23 import androidx.annotation.VisibleForTesting
24 import com.android.systemui.log.table.Diffable
25 import com.android.systemui.log.table.TableRowLogger
26 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
27 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
28 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.isValid
29 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.of
30 import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
31 import com.android.wifitrackerlib.WifiEntry
32 import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
33 
34 /** Provides information about the current wifi network. */
35 sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
36 
37     // TODO(b/238425913): Have a better, more unified strategy for diff-logging instead of
38     //   copy-pasting the column names for each sub-object.
39 
40     /**
41      * A model representing that we couldn't fetch any wifi information.
42      *
43      * This is only used with [DisabledWifiRepository], where [WifiManager] is null.
44      */
45     object Unavailable : WifiNetworkModel() {
toStringnull46         override fun toString() = "WifiNetwork.Unavailable"
47 
48         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
49             if (prevVal is Unavailable) {
50                 return
51             }
52 
53             logFull(row)
54         }
55 
logFullnull56         override fun logFull(row: TableRowLogger) {
57             row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE)
58             row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
59             row.logChange(COL_VALIDATED, false)
60             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
61             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
62             row.logChange(COL_SSID, null)
63             row.logChange(COL_HOTSPOT, null)
64         }
65     }
66 
67     /** A model representing that the wifi information we received was invalid in some way. */
68     data class Invalid(
69         /** A description of why the wifi information was invalid. */
70         val invalidReason: String,
71     ) : WifiNetworkModel() {
toStringnull72         override fun toString() = "WifiNetwork.Invalid[reason=$invalidReason]"
73 
74         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
75             if (prevVal !is Invalid) {
76                 logFull(row)
77                 return
78             }
79 
80             if (invalidReason != prevVal.invalidReason) {
81                 row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE[reason=$invalidReason]")
82             }
83         }
84 
logFullnull85         override fun logFull(row: TableRowLogger) {
86             row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE[reason=$invalidReason]")
87             row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
88             row.logChange(COL_VALIDATED, false)
89             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
90             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
91             row.logChange(COL_SSID, null)
92             row.logChange(COL_HOTSPOT, null)
93         }
94     }
95 
96     /** A model representing that we have no active wifi network. */
97     data class Inactive(
98         /** An optional description of why the wifi information was inactive. */
99         val inactiveReason: String? = null,
100     ) : WifiNetworkModel() {
toStringnull101         override fun toString() = "WifiNetwork.Inactive[reason=$inactiveReason]"
102 
103         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
104             if (prevVal !is Inactive) {
105                 logFull(row)
106                 return
107             }
108 
109             if (inactiveReason != prevVal.inactiveReason) {
110                 row.logChange(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=$inactiveReason]")
111             }
112         }
113 
logFullnull114         override fun logFull(row: TableRowLogger) {
115             row.logChange(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=$inactiveReason]")
116             row.logChange(COL_SUB_ID, SUB_ID_DEFAULT)
117             row.logChange(COL_VALIDATED, false)
118             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
119             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
120             row.logChange(COL_SSID, null)
121             row.logChange(COL_HOTSPOT, null)
122         }
123     }
124 
125     /**
126      * A model representing that our wifi network is actually a carrier merged network, meaning it's
127      * treated as more of a mobile network.
128      *
129      * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
130      *
131      * IMPORTANT: Do *not* call [copy] on this class. Instead, use the factory [of] methods. [of]
132      * will verify preconditions correctly.
133      */
134     data class CarrierMerged
135     private constructor(
136         /**
137          * The subscription ID that this connection represents.
138          *
139          * Comes from [android.net.wifi.WifiInfo.getSubscriptionId].
140          *
141          * Per that method, this value must not be [SubscriptionManager.INVALID_SUBSCRIPTION_ID] (if
142          * it was invalid, then this is *not* a carrier merged network).
143          */
144         val subscriptionId: Int,
145 
146         /** The signal level, required to be 0 <= level <= numberOfLevels. */
147         val level: Int,
148 
149         /** The maximum possible level. */
150         val numberOfLevels: Int,
151     ) : WifiNetworkModel() {
152         companion object {
153             /**
154              * Creates a [CarrierMerged] instance, or an [Invalid] instance if any of the arguments
155              * are invalid.
156              */
ofnull157             fun of(
158                 subscriptionId: Int,
159                 level: Int,
160                 numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS
161             ): WifiNetworkModel {
162                 if (!subscriptionId.isSubscriptionIdValid()) {
163                     return Invalid(INVALID_SUB_ID_ERROR_STRING)
164                 }
165                 if (!level.isLevelValid(numberOfLevels)) {
166                     return Invalid(getInvalidLevelErrorString(level, numberOfLevels))
167                 }
168                 return CarrierMerged(subscriptionId, level, numberOfLevels)
169             }
170 
isLevelValidnull171             private fun Int.isLevelValid(maxLevel: Int): Boolean {
172                 return this != WIFI_LEVEL_UNREACHABLE && this in MIN_VALID_LEVEL..maxLevel
173             }
174 
getInvalidLevelErrorStringnull175             private fun getInvalidLevelErrorString(level: Int, maxLevel: Int): String {
176                 return "Wifi network was carrier merged but had invalid level. " +
177                     "$MIN_VALID_LEVEL <= wifi level <= $maxLevel required; " +
178                     "level was $level"
179             }
180 
isSubscriptionIdValidnull181             private fun Int.isSubscriptionIdValid(): Boolean {
182                 return this != SubscriptionManager.INVALID_SUBSCRIPTION_ID
183             }
184 
185             private const val INVALID_SUB_ID_ERROR_STRING =
186                 "Wifi network was carrier merged but had invalid sub ID"
187         }
188 
189         init {
<lambda>null190             require(level.isLevelValid(numberOfLevels)) {
191                 "${getInvalidLevelErrorString(level, numberOfLevels)}. $DO_NOT_USE_COPY_ERROR"
192             }
<lambda>null193             require(subscriptionId.isSubscriptionIdValid()) {
194                 "$INVALID_SUB_ID_ERROR_STRING. $DO_NOT_USE_COPY_ERROR"
195             }
196         }
197 
logDiffsnull198         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
199             if (prevVal !is CarrierMerged) {
200                 logFull(row)
201                 return
202             }
203 
204             if (prevVal.subscriptionId != subscriptionId) {
205                 row.logChange(COL_SUB_ID, subscriptionId)
206             }
207             if (prevVal.level != level) {
208                 row.logChange(COL_LEVEL, level)
209             }
210             if (prevVal.numberOfLevels != numberOfLevels) {
211                 row.logChange(COL_NUM_LEVELS, numberOfLevels)
212             }
213         }
214 
logFullnull215         override fun logFull(row: TableRowLogger) {
216             row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
217             row.logChange(COL_SUB_ID, subscriptionId)
218             row.logChange(COL_VALIDATED, true)
219             row.logChange(COL_LEVEL, level)
220             row.logChange(COL_NUM_LEVELS, numberOfLevels)
221             row.logChange(COL_SSID, null)
222             row.logChange(COL_HOTSPOT, null)
223         }
224     }
225 
226     /**
227      * Provides information about an active wifi network.
228      *
229      * IMPORTANT: Do *not* call [copy] on this class. Instead, use the factory [of] method. [of]
230      * will verify preconditions correctly.
231      */
232     data class Active
233     private constructor(
234         /** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */
235         val isValidated: Boolean,
236 
237         /** The wifi signal level, required to be 0 <= level <= 4. */
238         val level: Int,
239 
240         /** See [android.net.wifi.WifiInfo.ssid]. */
241         val ssid: String?,
242 
243         /**
244          * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this
245          * isn't a hotspot connection.
246          */
247         val hotspotDeviceType: HotspotDeviceType,
248     ) : WifiNetworkModel() {
249         companion object {
250             /**
251              * Creates an [Active] instance, or an [Inactive] instance if any of the arguments are
252              * invalid.
253              */
254             @JvmStatic
ofnull255             fun of(
256                 isValidated: Boolean = false,
257                 level: Int,
258                 ssid: String? = null,
259                 hotspotDeviceType: HotspotDeviceType = HotspotDeviceType.NONE,
260             ): WifiNetworkModel {
261                 if (!level.isValid()) {
262                     return Inactive(getInvalidLevelErrorString(level))
263                 }
264                 return Active(isValidated, level, ssid, hotspotDeviceType)
265             }
266 
isValidnull267             private fun Int.isValid(): Boolean {
268                 return this != WIFI_LEVEL_UNREACHABLE && this in MIN_VALID_LEVEL..MAX_VALID_LEVEL
269             }
270 
getInvalidLevelErrorStringnull271             private fun getInvalidLevelErrorString(level: Int): String {
272                 return "Wifi network was active but had invalid level. " +
273                     "$MIN_VALID_LEVEL <= wifi level <= $MAX_VALID_LEVEL required; " +
274                     "level was $level"
275             }
276 
277             @VisibleForTesting internal const val MAX_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MAX
278         }
279 
280         init {
<lambda>null281             require(level.isValid()) {
282                 "${getInvalidLevelErrorString(level)}. $DO_NOT_USE_COPY_ERROR"
283             }
284         }
285 
286         /** Returns true if this network has a valid SSID and false otherwise. */
hasValidSsidnull287         fun hasValidSsid(): Boolean {
288             return ssid != null && ssid != UNKNOWN_SSID
289         }
290 
logDiffsnull291         override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
292             if (prevVal !is Active) {
293                 logFull(row)
294                 return
295             }
296 
297             if (prevVal.isValidated != isValidated) {
298                 row.logChange(COL_VALIDATED, isValidated)
299             }
300             if (prevVal.level != level) {
301                 row.logChange(COL_LEVEL, level)
302             }
303             if (prevVal.ssid != ssid) {
304                 row.logChange(COL_SSID, ssid)
305             }
306             if (prevVal.hotspotDeviceType != hotspotDeviceType) {
307                 row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
308             }
309         }
310 
logFullnull311         override fun logFull(row: TableRowLogger) {
312             row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
313             row.logChange(COL_SUB_ID, null)
314             row.logChange(COL_VALIDATED, isValidated)
315             row.logChange(COL_LEVEL, level)
316             row.logChange(COL_NUM_LEVELS, null)
317             row.logChange(COL_SSID, ssid)
318             row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
319         }
320     }
321 
322     companion object {
323         @VisibleForTesting internal const val MIN_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MIN
324     }
325 
326     /**
327      * Enum for the type of device providing the hotspot connection, or [NONE] if this connection
328      * isn't a hotspot.
329      */
330     enum class HotspotDeviceType {
331         /* This wifi connection isn't a hotspot. */
332         NONE,
333         /** The device type for this hotspot is unknown. */
334         UNKNOWN,
335         PHONE,
336         TABLET,
337         LAPTOP,
338         WATCH,
339         AUTO,
340         /** The device type sent for this hotspot is invalid to SysUI. */
341         INVALID,
342     }
343 
344     /**
345      * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to
346      * our internal representation.
347      */
toHotspotDeviceTypenull348     fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType {
349         return when (this) {
350             NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN
351             NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE
352             NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET
353             NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP
354             NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH
355             NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO
356             else -> HotspotDeviceType.INVALID
357         }
358     }
359 }
360 
361 const val TYPE_CARRIER_MERGED = "CarrierMerged"
362 const val TYPE_UNAVAILABLE = "Unavailable"
363 const val TYPE_INACTIVE = "Inactive"
364 const val TYPE_ACTIVE = "Active"
365 
366 const val COL_NETWORK_TYPE = "type"
367 const val COL_SUB_ID = "subscriptionId"
368 const val COL_VALIDATED = "isValidated"
369 const val COL_LEVEL = "level"
370 const val COL_NUM_LEVELS = "maxLevel"
371 const val COL_SSID = "ssid"
372 const val COL_HOTSPOT = "hotspot"
373 
374 val LEVEL_DEFAULT: String? = null
375 val NUM_LEVELS_DEFAULT: String? = null
376 val SUB_ID_DEFAULT: String? = null
377 
378 private const val DO_NOT_USE_COPY_ERROR =
379     "This should only be an issue if the caller incorrectly used `copy` to get a new instance. " +
380         "Please use the `of` method instead."
381