1 /* 2 * Copyright (C) 2019 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.wifitrackerlib; 18 19 import static android.net.wifi.WifiInfo.INVALID_RSSI; 20 21 import static androidx.core.util.Preconditions.checkNotNull; 22 23 import static com.android.wifitrackerlib.Utils.getNetworkPart; 24 import static com.android.wifitrackerlib.Utils.getSingleSecurityTypeFromMultipleSecurityTypes; 25 26 import android.content.Context; 27 import android.net.ConnectivityDiagnosticsManager; 28 import android.net.LinkAddress; 29 import android.net.LinkProperties; 30 import android.net.Network; 31 import android.net.NetworkCapabilities; 32 import android.net.NetworkInfo; 33 import android.net.RouteInfo; 34 import android.net.wifi.ScanResult; 35 import android.net.wifi.WifiConfiguration; 36 import android.net.wifi.WifiInfo; 37 import android.net.wifi.WifiManager; 38 import android.os.Handler; 39 import android.text.TextUtils; 40 41 import androidx.annotation.AnyThread; 42 import androidx.annotation.IntDef; 43 import androidx.annotation.MainThread; 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 import androidx.annotation.WorkerThread; 47 import androidx.core.os.BuildCompat; 48 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.net.Inet4Address; 52 import java.net.Inet6Address; 53 import java.net.InetAddress; 54 import java.net.UnknownHostException; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Collections; 58 import java.util.Comparator; 59 import java.util.List; 60 import java.util.Optional; 61 import java.util.StringJoiner; 62 import java.util.stream.Collectors; 63 64 /** 65 * Base class for an entry representing a Wi-Fi network in a Wi-Fi picker/settings. 66 * Subclasses should override the default methods for their own needs. 67 * 68 * Clients implementing a Wi-Fi picker/settings should receive WifiEntry objects from classes 69 * implementing BaseWifiTracker, and rely on the given API for all user-displayable information and 70 * actions on the represented network. 71 */ 72 public class WifiEntry { 73 /** 74 * Security type based on WifiConfiguration.KeyMgmt 75 */ 76 @Retention(RetentionPolicy.SOURCE) 77 @IntDef(value = { 78 SECURITY_NONE, 79 SECURITY_OWE, 80 SECURITY_WEP, 81 SECURITY_PSK, 82 SECURITY_SAE, 83 SECURITY_EAP, 84 SECURITY_EAP_SUITE_B, 85 SECURITY_EAP_WPA3_ENTERPRISE, 86 }) 87 88 public @interface Security {} 89 90 public static final int SECURITY_NONE = 0; 91 public static final int SECURITY_WEP = 1; 92 public static final int SECURITY_PSK = 2; 93 public static final int SECURITY_EAP = 3; 94 public static final int SECURITY_OWE = 4; 95 public static final int SECURITY_SAE = 5; 96 public static final int SECURITY_EAP_SUITE_B = 6; 97 public static final int SECURITY_EAP_WPA3_ENTERPRISE = 7; 98 99 public static final int NUM_SECURITY_TYPES = 8; 100 101 @Retention(RetentionPolicy.SOURCE) 102 @IntDef(value = { 103 CONNECTED_STATE_DISCONNECTED, 104 CONNECTED_STATE_CONNECTED, 105 CONNECTED_STATE_CONNECTING 106 }) 107 108 public @interface ConnectedState {} 109 110 public static final int CONNECTED_STATE_DISCONNECTED = 0; 111 public static final int CONNECTED_STATE_CONNECTING = 1; 112 public static final int CONNECTED_STATE_CONNECTED = 2; 113 114 // Wi-Fi signal levels for displaying signal strength. 115 public static final int WIFI_LEVEL_MIN = 0; 116 public static final int WIFI_LEVEL_MAX = 4; 117 public static final int WIFI_LEVEL_UNREACHABLE = -1; 118 119 @Retention(RetentionPolicy.SOURCE) 120 @IntDef(value = { 121 METERED_CHOICE_AUTO, 122 METERED_CHOICE_METERED, 123 METERED_CHOICE_UNMETERED, 124 }) 125 126 public @interface MeteredChoice {} 127 128 // User's choice whether to treat a network as metered. 129 public static final int METERED_CHOICE_AUTO = 0; 130 public static final int METERED_CHOICE_METERED = 1; 131 public static final int METERED_CHOICE_UNMETERED = 2; 132 133 @Retention(RetentionPolicy.SOURCE) 134 @IntDef(value = { 135 PRIVACY_DEVICE_MAC, 136 PRIVACY_RANDOMIZED_MAC, 137 PRIVACY_UNKNOWN 138 }) 139 140 public @interface Privacy {} 141 142 public static final int PRIVACY_DEVICE_MAC = 0; 143 public static final int PRIVACY_RANDOMIZED_MAC = 1; 144 public static final int PRIVACY_UNKNOWN = 2; 145 146 @Retention(RetentionPolicy.SOURCE) 147 @IntDef(value = { 148 FREQUENCY_2_4_GHZ, 149 FREQUENCY_5_GHZ, 150 FREQUENCY_6_GHZ, 151 FREQUENCY_60_GHZ, 152 FREQUENCY_UNKNOWN 153 }) 154 155 public @interface Frequency {} 156 157 public static final int FREQUENCY_2_4_GHZ = 2_400; 158 public static final int FREQUENCY_5_GHZ = 5_000; 159 public static final int FREQUENCY_6_GHZ = 6_000; 160 public static final int FREQUENCY_60_GHZ = 60_000; 161 public static final int FREQUENCY_UNKNOWN = -1; 162 163 /** 164 * Min bound on the 2.4 GHz (802.11b/g/n) WLAN channels. 165 */ 166 public static final int MIN_FREQ_24GHZ = 2400; 167 168 /** 169 * Max bound on the 2.4 GHz (802.11b/g/n) WLAN channels. 170 */ 171 public static final int MAX_FREQ_24GHZ = 2500; 172 173 /** 174 * Min bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels. 175 */ 176 public static final int MIN_FREQ_5GHZ = 4900; 177 178 /** 179 * Max bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels. 180 */ 181 public static final int MAX_FREQ_5GHZ = 5900; 182 183 /** 184 * Min bound on the 6.0 GHz (802.11ax) WLAN channels. 185 */ 186 public static final int MIN_FREQ_6GHZ = 5925; 187 188 /** 189 * Max bound on the 6.0 GHz (802.11ax) WLAN channels. 190 */ 191 public static final int MAX_FREQ_6GHZ = 7125; 192 193 /** 194 * Min bound on the 60 GHz (802.11ad) WLAN channels. 195 */ 196 public static final int MIN_FREQ_60GHZ = 58320; 197 198 /** 199 * Max bound on the 60 GHz (802.11ad) WLAN channels. 200 */ 201 public static final int MAX_FREQ_60GHZ = 70200; 202 203 /** 204 * Max ScanResult information displayed of Wi-Fi Verbose Logging. 205 */ 206 protected static final int MAX_VERBOSE_LOG_DISPLAY_SCANRESULT_COUNT = 4; 207 208 /** 209 * Default comparator for sorting WifiEntries on a Wi-Fi picker list. 210 */ 211 public static Comparator<WifiEntry> WIFI_PICKER_COMPARATOR = 212 Comparator.comparing((WifiEntry entry) -> !entry.isPrimaryNetwork()) 213 .thenComparing((WifiEntry entry) -> 214 entry.getConnectedState() != CONNECTED_STATE_CONNECTED) 215 .thenComparing((WifiEntry entry) -> !(entry instanceof KnownNetworkEntry)) 216 .thenComparing((WifiEntry entry) -> !(entry instanceof HotspotNetworkEntry)) 217 .thenComparing((WifiEntry entry) -> (entry instanceof HotspotNetworkEntry) 218 ? -((HotspotNetworkEntry) entry).getUpstreamConnectionStrength() : 0) 219 .thenComparing((WifiEntry entry) -> !entry.canConnect()) 220 .thenComparing((WifiEntry entry) -> !entry.isSubscription()) 221 .thenComparing((WifiEntry entry) -> !entry.isSaved()) 222 .thenComparing((WifiEntry entry) -> !entry.isSuggestion()) 223 .thenComparing((WifiEntry entry) -> -entry.getLevel()) 224 .thenComparing((WifiEntry entry) -> entry.getTitle()); 225 226 /** 227 * Default comparator for sorting WifiEntries by title. 228 */ 229 public static Comparator<WifiEntry> TITLE_COMPARATOR = 230 Comparator.comparing((WifiEntry entry) -> entry.getTitle()); 231 232 protected final boolean mForSavedNetworksPage; 233 234 @NonNull protected final WifiTrackerInjector mInjector; 235 @NonNull protected final Context mContext; 236 protected final WifiManager mWifiManager; 237 238 // Callback associated with this WifiEntry. Subclasses should call its methods appropriately. 239 private WifiEntryCallback mListener; 240 protected final Handler mCallbackHandler; 241 242 protected int mLevel = WIFI_LEVEL_UNREACHABLE; 243 protected WifiInfo mWifiInfo; 244 protected NetworkInfo mNetworkInfo; 245 protected Network mNetwork; 246 protected NetworkCapabilities mNetworkCapabilities; 247 protected NetworkCapabilities mDefaultNetworkCapabilities; 248 protected ConnectivityDiagnosticsManager.ConnectivityReport mConnectivityReport; 249 protected ConnectedInfo mConnectedInfo; 250 251 protected ConnectCallback mConnectCallback; 252 protected DisconnectCallback mDisconnectCallback; 253 protected ForgetCallback mForgetCallback; 254 255 protected boolean mCalledConnect = false; 256 protected boolean mCalledDisconnect = false; 257 258 protected boolean mIsDefaultNetwork; 259 260 private Optional<ManageSubscriptionAction> mManageSubscriptionAction = Optional.empty(); 261 WifiEntry(@onNull WifiTrackerInjector injector, @NonNull Handler callbackHandler, @NonNull WifiManager wifiManager, boolean forSavedNetworksPage)262 public WifiEntry(@NonNull WifiTrackerInjector injector, @NonNull Handler callbackHandler, 263 @NonNull WifiManager wifiManager, boolean forSavedNetworksPage) 264 throws IllegalArgumentException { 265 checkNotNull(injector, "Cannot construct with null injector!"); 266 checkNotNull(callbackHandler, "Cannot construct with null handler!"); 267 checkNotNull(wifiManager, "Cannot construct with null WifiManager!"); 268 mInjector = injector; 269 mContext = mInjector.getContext(); 270 mCallbackHandler = callbackHandler; 271 mForSavedNetworksPage = forSavedNetworksPage; 272 mWifiManager = wifiManager; 273 } 274 275 // Info available for all WifiEntries // 276 277 /** The unique key defining a WifiEntry */ 278 @NonNull getKey()279 public String getKey() { 280 return ""; 281 }; 282 283 /** Returns connection state of the network defined by the CONNECTED_STATE constants */ 284 @ConnectedState getConnectedState()285 public synchronized int getConnectedState() { 286 // If we have NetworkCapabilities, then we're L3 connected. 287 if (mNetworkCapabilities != null) { 288 return CONNECTED_STATE_CONNECTED; 289 } 290 291 // Use NetworkInfo to provide the connecting state before we're L3 connected. 292 if (mNetworkInfo != null) { 293 switch (mNetworkInfo.getDetailedState()) { 294 case SCANNING: 295 case CONNECTING: 296 case AUTHENTICATING: 297 case OBTAINING_IPADDR: 298 case VERIFYING_POOR_LINK: 299 case CAPTIVE_PORTAL_CHECK: 300 case CONNECTED: 301 return CONNECTED_STATE_CONNECTING; 302 default: 303 return CONNECTED_STATE_DISCONNECTED; 304 } 305 } 306 307 return CONNECTED_STATE_DISCONNECTED; 308 } 309 310 /** Returns the display title. This is most commonly the SSID of a network. */ 311 @NonNull getTitle()312 public String getTitle() { 313 return ""; 314 } 315 316 /** Returns the display summary, it's a concise summary. */ 317 @NonNull getSummary()318 public String getSummary() { 319 return getSummary(true /* concise */); 320 } 321 322 /** Returns the second summary, it's for additional information of the WifiEntry */ 323 @NonNull getSecondSummary()324 public CharSequence getSecondSummary() { 325 return ""; 326 } 327 328 /** 329 * Returns the display summary. 330 * @param concise Whether to show more information. e.g., verbose logging. 331 */ 332 @NonNull getSummary(boolean concise)333 public String getSummary(boolean concise) { 334 return ""; 335 }; 336 337 /** 338 * Returns the signal strength level within [WIFI_LEVEL_MIN, WIFI_LEVEL_MAX]. 339 * A value of WIFI_LEVEL_UNREACHABLE indicates an out of range network. 340 */ getLevel()341 public int getLevel() { 342 return mLevel; 343 }; 344 345 /** 346 * Returns whether the level icon for this network should show an X or not. 347 * By default, this means any connected network that has no/low-quality internet access. 348 */ shouldShowXLevelIcon()349 public boolean shouldShowXLevelIcon() { 350 return getConnectedState() != CONNECTED_STATE_DISCONNECTED 351 && mConnectivityReport != null 352 && (!hasInternetAccess() || isLowQuality()) 353 && !canSignIn() 354 && isPrimaryNetwork(); 355 } 356 357 /** 358 * Returns whether this network has validated internet access or not. 359 * Note: This does not necessarily mean the network is the default route. 360 */ hasInternetAccess()361 public boolean hasInternetAccess() { 362 return mNetworkCapabilities != null 363 && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); 364 } 365 366 /** 367 * Returns whether this network is the default network or not (i.e. this network is the one 368 * currently being used to provide internet connection). 369 */ isDefaultNetwork()370 public boolean isDefaultNetwork() { 371 return mIsDefaultNetwork; 372 } 373 374 /** 375 * Returns whether this network is the primary Wi-Fi network or not. 376 */ isPrimaryNetwork()377 public boolean isPrimaryNetwork() { 378 if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) { 379 // In case we have mNetworkInfo but the state is disconnected. 380 return false; 381 } 382 return mNetworkInfo != null 383 || (mWifiInfo != null && NonSdkApiWrapper.isPrimary(mWifiInfo)); 384 } 385 386 /** 387 * Returns whether this network is considered low quality. 388 */ isLowQuality()389 public boolean isLowQuality() { 390 if (!isPrimaryNetwork()) { 391 return false; 392 } 393 if (mNetworkCapabilities == null) { 394 return false; 395 } 396 if (mDefaultNetworkCapabilities == null) { 397 return false; 398 } 399 return mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) 400 && mDefaultNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) 401 && !mDefaultNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); 402 } 403 404 /** 405 * Returns the SSID of the entry, if applicable. Null otherwise. 406 */ 407 @Nullable getSsid()408 public String getSsid() { 409 return null; 410 } 411 412 /** 413 * Returns the security type defined by the SECURITY constants 414 * @deprecated Use getSecurityTypes() which can return multiple security types. 415 */ 416 // TODO(b/187554920): Remove this and move all clients to getSecurityTypes() 417 @Deprecated 418 @Security getSecurity()419 public int getSecurity() { 420 switch (getSingleSecurityTypeFromMultipleSecurityTypes(getSecurityTypes())) { 421 case WifiInfo.SECURITY_TYPE_OPEN: 422 return SECURITY_NONE; 423 case WifiInfo.SECURITY_TYPE_OWE: 424 return SECURITY_OWE; 425 case WifiInfo.SECURITY_TYPE_WEP: 426 return SECURITY_WEP; 427 case WifiInfo.SECURITY_TYPE_PSK: 428 return SECURITY_PSK; 429 case WifiInfo.SECURITY_TYPE_SAE: 430 return SECURITY_SAE; 431 case WifiInfo.SECURITY_TYPE_EAP: 432 return SECURITY_EAP; 433 case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE: 434 return SECURITY_EAP_WPA3_ENTERPRISE; 435 case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT: 436 return SECURITY_EAP_SUITE_B; 437 case WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2: 438 case WifiInfo.SECURITY_TYPE_PASSPOINT_R3: 439 return SECURITY_EAP; 440 default: 441 return SECURITY_NONE; 442 } 443 } 444 445 /** 446 * Returns security type of the current connection, or the available types for connection 447 * in the form of the SECURITY_TYPE_* values in {@link WifiInfo} 448 */ 449 @NonNull getSecurityTypes()450 public List<Integer> getSecurityTypes() { 451 return Collections.emptyList(); 452 } 453 454 /** Returns the MAC address of the connection */ 455 @Nullable getMacAddress()456 public String getMacAddress() { 457 return null; 458 } 459 460 /** 461 * Indicates when a network is metered or the user marked the network as metered. 462 */ isMetered()463 public boolean isMetered() { 464 return false; 465 } 466 467 /** 468 * Indicates whether or not an entry is for a saved configuration. 469 */ isSaved()470 public boolean isSaved() { 471 return false; 472 } 473 474 /** 475 * Indicates whether or not an entry is for a saved configuration. 476 */ isSuggestion()477 public boolean isSuggestion() { 478 return false; 479 } 480 481 /** 482 * Indicates whether or not an entry is for a subscription. 483 */ isSubscription()484 public boolean isSubscription() { 485 return false; 486 } 487 488 /** 489 * Returns the WifiConfiguration of an entry or null if unavailable. This should be used when 490 * information on the WifiConfiguration needs to be modified and saved via 491 * {@link WifiManager#save(WifiConfiguration, WifiManager.ActionListener)}. 492 */ 493 @Nullable getWifiConfiguration()494 public WifiConfiguration getWifiConfiguration() { 495 return null; 496 } 497 498 /** 499 * Returns the ConnectedInfo object pertaining to an active connection. 500 * 501 * Returns null if getConnectedState() != CONNECTED_STATE_CONNECTED. 502 */ 503 @Nullable getConnectedInfo()504 public synchronized ConnectedInfo getConnectedInfo() { 505 if (getConnectedState() != CONNECTED_STATE_CONNECTED) { 506 return null; 507 } 508 509 return new ConnectedInfo(mConnectedInfo); 510 } 511 512 /** 513 * Info associated with the active connection. 514 */ 515 public static class ConnectedInfo { 516 @Frequency 517 public int frequencyMhz; 518 public List<String> dnsServers = new ArrayList<>(); 519 public int linkSpeedMbps; 520 public String ipAddress; 521 public List<String> ipv6Addresses = new ArrayList<>(); 522 public String gateway; 523 public String subnetMask; 524 public int wifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN; 525 public NetworkCapabilities networkCapabilities; 526 527 /** 528 * Creates an empty ConnectedInfo 529 */ ConnectedInfo()530 public ConnectedInfo() { 531 } 532 533 /** 534 * Creates a ConnectedInfo with all fields copied from an input ConnectedInfo 535 */ ConnectedInfo(@onNull ConnectedInfo other)536 public ConnectedInfo(@NonNull ConnectedInfo other) { 537 frequencyMhz = other.frequencyMhz; 538 dnsServers = new ArrayList<>(dnsServers); 539 linkSpeedMbps = other.linkSpeedMbps; 540 ipAddress = other.ipAddress; 541 ipv6Addresses = new ArrayList<>(other.ipv6Addresses); 542 gateway = other.gateway; 543 subnetMask = other.subnetMask; 544 wifiStandard = other.wifiStandard; 545 networkCapabilities = other.networkCapabilities; 546 } 547 } 548 549 // User actions on a network 550 551 /** Returns whether the entry should show a connect option */ canConnect()552 public boolean canConnect() { 553 return false; 554 } 555 556 /** Connects to the network */ connect(@ullable ConnectCallback callback)557 public void connect(@Nullable ConnectCallback callback) { 558 // Do nothing. 559 } 560 561 /** Returns whether the entry should show a disconnect option */ canDisconnect()562 public boolean canDisconnect() { 563 return false; 564 } 565 566 /** Disconnects from the network */ disconnect(@ullable DisconnectCallback callback)567 public void disconnect(@Nullable DisconnectCallback callback) { 568 // Do nothing. 569 } 570 571 /** Returns whether the entry should show a forget option */ canForget()572 public boolean canForget() { 573 return false; 574 } 575 576 /** Forgets the network */ forget(@ullable ForgetCallback callback)577 public void forget(@Nullable ForgetCallback callback) { 578 // Do nothing. 579 } 580 581 /** Returns whether the network can be signed-in to */ canSignIn()582 public boolean canSignIn() { 583 return false; 584 } 585 586 /** Sign-in to the network. For captive portals. */ signIn(@ullable SignInCallback callback)587 public void signIn(@Nullable SignInCallback callback) { 588 // Do nothing. 589 } 590 591 /** Returns whether the network can be shared via QR code */ canShare()592 public boolean canShare() { 593 return false; 594 } 595 596 /** Returns whether the user can use Easy Connect to onboard a device to the network */ canEasyConnect()597 public boolean canEasyConnect() { 598 return false; 599 } 600 601 // Modifiable settings 602 603 /** 604 * Returns the user's choice whether to treat a network as metered, 605 * defined by the METERED_CHOICE constants 606 */ 607 @MeteredChoice getMeteredChoice()608 public int getMeteredChoice() { 609 return METERED_CHOICE_AUTO; 610 } 611 612 /** Returns whether the entry should let the user choose the metered treatment of a network */ canSetMeteredChoice()613 public boolean canSetMeteredChoice() { 614 return false; 615 } 616 617 /** 618 * Sets the user's choice for treating a network as metered, 619 * defined by the METERED_CHOICE constants 620 */ setMeteredChoice(@eteredChoice int meteredChoice)621 public void setMeteredChoice(@MeteredChoice int meteredChoice) { 622 // Do nothing. 623 } 624 625 /** Returns whether the entry should let the user choose the MAC randomization setting */ canSetPrivacy()626 public boolean canSetPrivacy() { 627 return false; 628 } 629 630 /** Returns the MAC randomization setting defined by the PRIVACY constants */ 631 @Privacy getPrivacy()632 public int getPrivacy() { 633 return PRIVACY_UNKNOWN; 634 } 635 636 /** Sets the user's choice for MAC randomization defined by the PRIVACY constants */ setPrivacy(@rivacy int privacy)637 public void setPrivacy(@Privacy int privacy) { 638 // Do nothing. 639 } 640 641 /** Returns whether the network has auto-join enabled */ isAutoJoinEnabled()642 public boolean isAutoJoinEnabled() { 643 return false; 644 } 645 646 /** Returns whether the user can enable/disable auto-join */ canSetAutoJoinEnabled()647 public boolean canSetAutoJoinEnabled() { 648 return false; 649 } 650 651 /** Sets whether a network will be auto-joined or not */ setAutoJoinEnabled(boolean enabled)652 public void setAutoJoinEnabled(boolean enabled) { 653 // Do nothing. 654 } 655 656 /** Returns the string displayed for @Security */ getSecurityString(boolean concise)657 public String getSecurityString(boolean concise) { 658 return ""; 659 } 660 661 /** Returns the string displayed for the Wi-Fi standard */ getStandardString()662 public String getStandardString() { 663 return ""; 664 } 665 666 /** Returns the string displayed for the Wi-Fi band */ getBandString()667 public String getBandString() { 668 return ""; 669 } 670 671 /** 672 * Returns the string displayed for Tx link speed. 673 */ getTxSpeedString()674 public String getTxSpeedString() { 675 return Utils.getSpeedString(mContext, mWifiInfo, /* isTx */ true); 676 } 677 678 /** 679 * Returns the string displayed for Rx link speed. 680 */ getRxSpeedString()681 public String getRxSpeedString() { 682 return Utils.getSpeedString(mContext, mWifiInfo, /* isTx */ false); 683 } 684 685 /** Returns whether subscription of the entry is expired */ isExpired()686 public boolean isExpired() { 687 return false; 688 } 689 690 691 /** Returns whether a user can manage their subscription through this WifiEntry */ canManageSubscription()692 public boolean canManageSubscription() { 693 return mManageSubscriptionAction.isPresent(); 694 }; 695 696 /** 697 * Return the URI string value of help, if it is not null, WifiPicker may show 698 * help icon and route the user to help page specified by the URI string. 699 * see {@link Intent#parseUri} 700 */ 701 @Nullable getHelpUriString()702 public String getHelpUriString() { 703 return null; 704 } 705 706 /** Allows the user to manage their subscription via an external flow */ manageSubscription()707 public void manageSubscription() { 708 mManageSubscriptionAction.ifPresent(ManageSubscriptionAction::onExecute); 709 }; 710 711 /** Set the action to be called on calling WifiEntry#manageSubscription. */ setManageSubscriptionAction( @onNull ManageSubscriptionAction manageSubscriptionAction)712 public void setManageSubscriptionAction( 713 @NonNull ManageSubscriptionAction manageSubscriptionAction) { 714 // only notify update on 1st time 715 boolean notify = !mManageSubscriptionAction.isPresent(); 716 717 mManageSubscriptionAction = Optional.of(manageSubscriptionAction); 718 if (notify) { 719 notifyOnUpdated(); 720 } 721 } 722 723 /** Returns the ScanResult information of a WifiEntry */ 724 @NonNull getScanResultDescription()725 protected String getScanResultDescription() { 726 return ""; 727 } 728 729 /** Returns the network selection information of a WifiEntry */ 730 @NonNull getNetworkSelectionDescription()731 String getNetworkSelectionDescription() { 732 return ""; 733 } 734 735 /** Returns the network capability information of a WifiEntry */ 736 @NonNull getNetworkCapabilityDescription()737 String getNetworkCapabilityDescription() { 738 final StringBuilder sb = new StringBuilder(); 739 if (getConnectedState() == CONNECTED_STATE_CONNECTED) { 740 sb.append("hasInternet:") 741 .append(hasInternetAccess()) 742 .append(", isDefaultNetwork:") 743 .append(mIsDefaultNetwork) 744 .append(", isLowQuality:") 745 .append(isLowQuality()); 746 } 747 return sb.toString(); 748 } 749 750 /** 751 * In Wi-Fi picker, when users click a saved network, it will connect to the Wi-Fi network. 752 * However, for some special cases, Wi-Fi picker should show Wi-Fi editor UI for users to edit 753 * security or password before connecting. Or users will always get connection fail results. 754 */ shouldEditBeforeConnect()755 public boolean shouldEditBeforeConnect() { 756 return false; 757 } 758 759 /** 760 * Whether there are admin restrictions preventing connection to this network. 761 */ hasAdminRestrictions()762 public boolean hasAdminRestrictions() { 763 return false; 764 } 765 766 /** 767 * Sets the callback listener for WifiEntryCallback methods. 768 * Subsequent calls will overwrite the previous listener. 769 */ setListener(WifiEntryCallback listener)770 public synchronized void setListener(WifiEntryCallback listener) { 771 mListener = listener; 772 } 773 774 /** 775 * Listener for changes to the state of the WifiEntry. 776 * This callback will be invoked on the main thread. 777 */ 778 public interface WifiEntryCallback { 779 /** 780 * Indicates the state of the WifiEntry has changed and clients may retrieve updates through 781 * the WifiEntry getter methods. 782 */ 783 @MainThread onUpdated()784 void onUpdated(); 785 } 786 787 @AnyThread notifyOnUpdated()788 protected void notifyOnUpdated() { 789 if (mListener != null) { 790 mCallbackHandler.post(() -> { 791 final WifiEntryCallback listener = mListener; 792 if (listener != null) { 793 listener.onUpdated(); 794 } 795 }); 796 } 797 } 798 799 /** 800 * Listener for changes to the state of the WifiEntry. 801 * This callback will be invoked on the main thread. 802 */ 803 public interface ConnectCallback { 804 @Retention(RetentionPolicy.SOURCE) 805 @IntDef(value = { 806 CONNECT_STATUS_SUCCESS, 807 CONNECT_STATUS_FAILURE_NO_CONFIG, 808 CONNECT_STATUS_FAILURE_UNKNOWN, 809 CONNECT_STATUS_FAILURE_SIM_ABSENT 810 }) 811 812 public @interface ConnectStatus {} 813 814 int CONNECT_STATUS_SUCCESS = 0; 815 int CONNECT_STATUS_FAILURE_NO_CONFIG = 1; 816 int CONNECT_STATUS_FAILURE_UNKNOWN = 2; 817 int CONNECT_STATUS_FAILURE_SIM_ABSENT = 3; 818 819 /** 820 * Result of the connect request indicated by the CONNECT_STATUS constants. 821 */ 822 @MainThread onConnectResult(@onnectStatus int status)823 void onConnectResult(@ConnectStatus int status); 824 } 825 826 /** 827 * Listener for changes to the state of the WifiEntry. 828 * This callback will be invoked on the main thread. 829 */ 830 public interface DisconnectCallback { 831 @Retention(RetentionPolicy.SOURCE) 832 @IntDef(value = { 833 DISCONNECT_STATUS_SUCCESS, 834 DISCONNECT_STATUS_FAILURE_UNKNOWN 835 }) 836 837 public @interface DisconnectStatus {} 838 839 int DISCONNECT_STATUS_SUCCESS = 0; 840 int DISCONNECT_STATUS_FAILURE_UNKNOWN = 1; 841 /** 842 * Result of the disconnect request indicated by the DISCONNECT_STATUS constants. 843 */ 844 @MainThread onDisconnectResult(@isconnectStatus int status)845 void onDisconnectResult(@DisconnectStatus int status); 846 } 847 848 /** 849 * Listener for changes to the state of the WifiEntry. 850 * This callback will be invoked on the main thread. 851 */ 852 public interface ForgetCallback { 853 @Retention(RetentionPolicy.SOURCE) 854 @IntDef(value = { 855 FORGET_STATUS_SUCCESS, 856 FORGET_STATUS_FAILURE_UNKNOWN 857 }) 858 859 public @interface ForgetStatus {} 860 861 int FORGET_STATUS_SUCCESS = 0; 862 int FORGET_STATUS_FAILURE_UNKNOWN = 1; 863 864 /** 865 * Result of the forget request indicated by the FORGET_STATUS constants. 866 */ 867 @MainThread onForgetResult(@orgetStatus int status)868 void onForgetResult(@ForgetStatus int status); 869 } 870 871 /** 872 * Listener for changes to the state of the WifiEntry. 873 * This callback will be invoked on the main thread. 874 */ 875 public interface SignInCallback { 876 @Retention(RetentionPolicy.SOURCE) 877 @IntDef(value = { 878 SIGNIN_STATUS_SUCCESS, 879 SIGNIN_STATUS_FAILURE_UNKNOWN 880 }) 881 882 public @interface SignInStatus {} 883 884 int SIGNIN_STATUS_SUCCESS = 0; 885 int SIGNIN_STATUS_FAILURE_UNKNOWN = 1; 886 887 /** 888 * Result of the sign-in request indicated by the SIGNIN_STATUS constants. 889 */ 890 @MainThread onSignInResult(@ignInStatus int status)891 void onSignInResult(@SignInStatus int status); 892 } 893 894 /** 895 * Returns whether the supplied WifiInfo represents this WifiEntry 896 */ connectionInfoMatches(@onNull WifiInfo wifiInfo)897 protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo) { 898 return false; 899 } 900 901 /** 902 * Updates this WifiEntry with the given primary WifiInfo/NetworkInfo if they match. 903 * @param primaryWifiInfo Primary WifiInfo that has changed 904 * @param networkInfo NetworkInfo of the primary network if available 905 */ onPrimaryWifiInfoChanged( @ullable WifiInfo primaryWifiInfo, @Nullable NetworkInfo networkInfo)906 synchronized void onPrimaryWifiInfoChanged( 907 @Nullable WifiInfo primaryWifiInfo, @Nullable NetworkInfo networkInfo) { 908 if (primaryWifiInfo == null || !connectionInfoMatches(primaryWifiInfo)) { 909 if (mNetworkInfo != null) { 910 mNetworkInfo = null; 911 notifyOnUpdated(); 912 } 913 return; 914 } 915 mWifiInfo = primaryWifiInfo; 916 if (networkInfo != null) { 917 mNetworkInfo = networkInfo; 918 } 919 notifyOnUpdated(); 920 } 921 922 /** 923 * Updates this WifiEntry with the given NetworkCapabilities if it matches. 924 */ 925 @WorkerThread onNetworkCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities capabilities)926 synchronized void onNetworkCapabilitiesChanged( 927 @NonNull Network network, 928 @NonNull NetworkCapabilities capabilities) { 929 WifiInfo wifiInfo = Utils.getWifiInfo(capabilities); 930 if (wifiInfo == null) { 931 return; 932 } 933 934 if (!connectionInfoMatches(wifiInfo)) { 935 if (network.equals(mNetwork)) { 936 // WifiInfo doesn't match but the Network matches. This may be due to linked 937 // roaming, so treat as a disconnect. 938 onNetworkLost(network); 939 } 940 return; 941 } 942 943 // Treat non-primary, non-OEM connections as disconnected. 944 if (!NonSdkApiWrapper.isPrimary(wifiInfo) 945 && !NonSdkApiWrapper.isOemCapabilities(capabilities)) { 946 onNetworkLost(network); 947 return; 948 } 949 950 // Connection info matches, so the Network/NetworkCapabilities represent this network 951 // and the network is currently connecting or connected. 952 mWifiInfo = wifiInfo; 953 mNetwork = network; 954 mNetworkCapabilities = capabilities; 955 final int wifiInfoRssi = mWifiInfo.getRssi(); 956 if (wifiInfoRssi != INVALID_RSSI) { 957 mLevel = mWifiManager.calculateSignalLevel(wifiInfoRssi); 958 } 959 if (getConnectedState() == CONNECTED_STATE_CONNECTED) { 960 if (mCalledConnect) { 961 mCalledConnect = false; 962 mCallbackHandler.post(() -> { 963 final ConnectCallback connectCallback = mConnectCallback; 964 if (connectCallback != null) { 965 connectCallback.onConnectResult( 966 ConnectCallback.CONNECT_STATUS_SUCCESS); 967 } 968 }); 969 } 970 971 if (mConnectedInfo == null) { 972 mConnectedInfo = new ConnectedInfo(); 973 } 974 mConnectedInfo.frequencyMhz = mWifiInfo.getFrequency(); 975 mConnectedInfo.linkSpeedMbps = mWifiInfo.getLinkSpeed(); 976 mConnectedInfo.wifiStandard = mWifiInfo.getWifiStandard(); 977 } 978 updateSecurityTypes(); 979 notifyOnUpdated(); 980 } 981 982 /** 983 * Updates this WifiEntry as disconnected if the network matches. 984 * @param network Network that was lost 985 */ onNetworkLost(@onNull Network network)986 synchronized void onNetworkLost(@NonNull Network network) { 987 if (!network.equals(mNetwork)) { 988 return; 989 } 990 991 // Network matches, so this network is disconnected. 992 mWifiInfo = null; 993 mNetworkInfo = null; 994 mNetworkCapabilities = null; 995 mConnectedInfo = null; 996 mConnectivityReport = null; 997 mIsDefaultNetwork = false; 998 if (mCalledDisconnect) { 999 mCalledDisconnect = false; 1000 mCallbackHandler.post(() -> { 1001 final DisconnectCallback disconnectCallback = mDisconnectCallback; 1002 if (disconnectCallback != null) { 1003 disconnectCallback.onDisconnectResult( 1004 DisconnectCallback.DISCONNECT_STATUS_SUCCESS); 1005 } 1006 }); 1007 } 1008 updateSecurityTypes(); 1009 notifyOnUpdated(); 1010 } 1011 1012 /** 1013 * Updates this WifiEntry as the default network if it matches. 1014 */ 1015 @WorkerThread onDefaultNetworkCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities capabilities)1016 synchronized void onDefaultNetworkCapabilitiesChanged( 1017 @NonNull Network network, 1018 @NonNull NetworkCapabilities capabilities) { 1019 onNetworkCapabilitiesChanged(network, capabilities); 1020 mDefaultNetworkCapabilities = capabilities; 1021 mIsDefaultNetwork = network.equals(mNetwork); 1022 notifyOnUpdated(); 1023 } 1024 1025 /** 1026 * Notifies this WifiEntry that the default network was lost. 1027 */ onDefaultNetworkLost()1028 synchronized void onDefaultNetworkLost() { 1029 mDefaultNetworkCapabilities = null; 1030 mIsDefaultNetwork = false; 1031 notifyOnUpdated(); 1032 } 1033 1034 // Called to indicate the security types should be updated to match new information about the 1035 // network. updateSecurityTypes()1036 protected void updateSecurityTypes() { 1037 // Do nothing; 1038 } 1039 1040 // Updates this WifiEntry's link properties if the network matches. 1041 @WorkerThread updateLinkProperties( @onNull Network network, @NonNull LinkProperties linkProperties)1042 synchronized void updateLinkProperties( 1043 @NonNull Network network, @NonNull LinkProperties linkProperties) { 1044 if (!network.equals(mNetwork)) { 1045 return; 1046 } 1047 1048 if (mConnectedInfo == null) { 1049 mConnectedInfo = new ConnectedInfo(); 1050 } 1051 // Find IPv4 and IPv6 addresses, and subnet mask 1052 List<String> ipv6Addresses = new ArrayList<>(); 1053 for (LinkAddress addr : linkProperties.getLinkAddresses()) { 1054 if (addr.getAddress() instanceof Inet4Address) { 1055 mConnectedInfo.ipAddress = addr.getAddress().getHostAddress(); 1056 try { 1057 InetAddress all = InetAddress.getByAddress( 1058 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255}); 1059 mConnectedInfo.subnetMask = getNetworkPart( 1060 all, addr.getPrefixLength()).getHostAddress(); 1061 } catch (UnknownHostException | IllegalArgumentException e) { 1062 // Leave subnet null; 1063 } 1064 } else if (addr.getAddress() instanceof Inet6Address) { 1065 ipv6Addresses.add(addr.getAddress().getHostAddress()); 1066 } 1067 } 1068 mConnectedInfo.ipv6Addresses = ipv6Addresses; 1069 1070 // Find IPv4 default gateway. 1071 for (RouteInfo routeInfo : linkProperties.getRoutes()) { 1072 if (routeInfo.isDefaultRoute() && routeInfo.getDestination().getAddress() 1073 instanceof Inet4Address && routeInfo.hasGateway()) { 1074 mConnectedInfo.gateway = routeInfo.getGateway().getHostAddress(); 1075 break; 1076 } 1077 } 1078 1079 // Find DNS servers 1080 mConnectedInfo.dnsServers = linkProperties.getDnsServers().stream() 1081 .map(InetAddress::getHostAddress).collect(Collectors.toList()); 1082 1083 notifyOnUpdated(); 1084 } 1085 1086 // Method for WifiTracker to update a connected WifiEntry's validation status. 1087 @WorkerThread updateConnectivityReport( @onNull ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport)1088 synchronized void updateConnectivityReport( 1089 @NonNull ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport) { 1090 if (connectivityReport.getNetwork().equals(mNetwork)) { 1091 mConnectivityReport = connectivityReport; 1092 notifyOnUpdated(); 1093 } 1094 } 1095 getWifiInfoDescription()1096 synchronized String getWifiInfoDescription() { 1097 final StringJoiner sj = new StringJoiner(" "); 1098 if (getConnectedState() == CONNECTED_STATE_CONNECTED && mWifiInfo != null) { 1099 sj.add("f = " + mWifiInfo.getFrequency()); 1100 final String bssid = mWifiInfo.getBSSID(); 1101 if (bssid != null) { 1102 sj.add(bssid); 1103 } 1104 sj.add("standard = " + getStandardString()); 1105 sj.add("rssi = " + mWifiInfo.getRssi()); 1106 sj.add("score = " + mWifiInfo.getScore()); 1107 sj.add(String.format(" tx=%.1f,", mWifiInfo.getSuccessfulTxPacketsPerSecond())); 1108 sj.add(String.format("%.1f,", mWifiInfo.getRetriedTxPacketsPerSecond())); 1109 sj.add(String.format("%.1f ", mWifiInfo.getLostTxPacketsPerSecond())); 1110 sj.add(String.format("rx=%.1f", mWifiInfo.getSuccessfulRxPacketsPerSecond())); 1111 if (BuildCompat.isAtLeastT() && mWifiInfo.getApMldMacAddress() != null) { 1112 sj.add("mldMac = " + mWifiInfo.getApMldMacAddress()); 1113 sj.add("linkId = " + mWifiInfo.getApMloLinkId()); 1114 sj.add("affLinks = " + Arrays.toString( 1115 mWifiInfo.getAffiliatedMloLinks().toArray())); 1116 } 1117 } 1118 return sj.toString(); 1119 } 1120 1121 protected class ConnectActionListener implements WifiManager.ActionListener { 1122 @Override onSuccess()1123 public void onSuccess() { 1124 synchronized (WifiEntry.this) { 1125 // Wait for L3 connection before returning the success result. 1126 mCalledConnect = true; 1127 } 1128 } 1129 1130 @Override onFailure(int i)1131 public void onFailure(int i) { 1132 mCallbackHandler.post(() -> { 1133 final ConnectCallback connectCallback = mConnectCallback; 1134 if (connectCallback != null) { 1135 connectCallback.onConnectResult(ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN); 1136 } 1137 }); 1138 } 1139 } 1140 1141 protected class ForgetActionListener implements WifiManager.ActionListener { 1142 @Override onSuccess()1143 public void onSuccess() { 1144 mCallbackHandler.post(() -> { 1145 final ForgetCallback forgetCallback = mForgetCallback; 1146 if (forgetCallback != null) { 1147 forgetCallback.onForgetResult(ForgetCallback.FORGET_STATUS_SUCCESS); 1148 } 1149 }); 1150 } 1151 1152 @Override onFailure(int i)1153 public void onFailure(int i) { 1154 mCallbackHandler.post(() -> { 1155 final ForgetCallback forgetCallback = mForgetCallback; 1156 if (forgetCallback != null) { 1157 forgetCallback.onForgetResult(ForgetCallback.FORGET_STATUS_FAILURE_UNKNOWN); 1158 } 1159 }); 1160 } 1161 } 1162 1163 @Override equals(Object other)1164 public boolean equals(Object other) { 1165 if (!(other instanceof WifiEntry)) return false; 1166 return getKey().equals(((WifiEntry) other).getKey()); 1167 } 1168 1169 @Override hashCode()1170 public int hashCode() { 1171 return getKey().hashCode(); 1172 } 1173 1174 @Override toString()1175 public String toString() { 1176 StringJoiner sj = new StringJoiner("][", "[", "]"); 1177 sj.add(this.getClass().getSimpleName()); 1178 sj.add(getTitle()); 1179 sj.add(getSummary()); 1180 sj.add("Level:" + getLevel() + (shouldShowXLevelIcon() ? "!" : "")); 1181 String security = getSecurityString(true); 1182 if (!TextUtils.isEmpty(security)) { 1183 sj.add(security); 1184 } 1185 int connectedState = getConnectedState(); 1186 if (connectedState == CONNECTED_STATE_CONNECTED) { 1187 sj.add("Connected"); 1188 } else if (connectedState == CONNECTED_STATE_CONNECTING) { 1189 sj.add("Connecting..."); 1190 } 1191 if (hasInternetAccess()) { 1192 sj.add("Internet"); 1193 } 1194 if (isDefaultNetwork()) { 1195 sj.add("Default"); 1196 } 1197 if (isPrimaryNetwork()) { 1198 sj.add("Primary"); 1199 } 1200 if (isLowQuality()) { 1201 sj.add("LowQuality"); 1202 } 1203 if (isSaved()) { 1204 sj.add("Saved"); 1205 } 1206 if (isSubscription()) { 1207 sj.add("Subscription"); 1208 } 1209 if (isSuggestion()) { 1210 sj.add("Suggestion"); 1211 } 1212 if (isMetered()) { 1213 sj.add("Metered"); 1214 } 1215 if ((isSaved() || isSuggestion() || isSubscription()) && !isAutoJoinEnabled()) { 1216 sj.add("AutoJoinDisabled"); 1217 } 1218 if (isExpired()) { 1219 sj.add("Expired"); 1220 } 1221 if (canSignIn()) { 1222 sj.add("SignIn"); 1223 } 1224 if (shouldEditBeforeConnect()) { 1225 sj.add("EditBeforeConnect"); 1226 } 1227 if (hasAdminRestrictions()) { 1228 sj.add("AdminRestricted"); 1229 } 1230 return sj.toString(); 1231 } 1232 1233 /** 1234 * The action used to execute the calling of WifiEntry#manageSubscription. 1235 */ 1236 public interface ManageSubscriptionAction { 1237 /** 1238 * Execute the action of managing subscription. 1239 */ onExecute()1240 void onExecute(); 1241 } 1242 } 1243