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.getSpeedFromWifiInfo; 24 25 import android.net.LinkAddress; 26 import android.net.LinkProperties; 27 import android.net.NetworkCapabilities; 28 import android.net.NetworkInfo; 29 import android.net.NetworkUtils; 30 import android.net.RouteInfo; 31 import android.net.wifi.WifiConfiguration; 32 import android.net.wifi.WifiInfo; 33 import android.net.wifi.WifiManager; 34 import android.net.wifi.WifiNetworkScoreCache; 35 import android.os.Handler; 36 37 import androidx.annotation.AnyThread; 38 import androidx.annotation.IntDef; 39 import androidx.annotation.MainThread; 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 import androidx.annotation.VisibleForTesting; 43 import androidx.annotation.WorkerThread; 44 45 import java.lang.annotation.Retention; 46 import java.lang.annotation.RetentionPolicy; 47 import java.net.Inet4Address; 48 import java.net.Inet6Address; 49 import java.net.InetAddress; 50 import java.net.UnknownHostException; 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.StringJoiner; 54 import java.util.stream.Collectors; 55 56 /** 57 * Abstract base class for an entry representing a Wi-Fi network in a Wi-Fi picker/settings. 58 * 59 * Clients implementing a Wi-Fi picker/settings should receive WifiEntry objects from classes 60 * implementing BaseWifiTracker, and rely on the given API for all user-displayable information and 61 * actions on the represented network. 62 */ 63 public abstract class WifiEntry implements Comparable<WifiEntry> { 64 /** 65 * Security type based on WifiConfiguration.KeyMgmt 66 */ 67 @Retention(RetentionPolicy.SOURCE) 68 @IntDef(value = { 69 SECURITY_NONE, 70 SECURITY_OWE, 71 SECURITY_WEP, 72 SECURITY_PSK, 73 SECURITY_SAE, 74 SECURITY_EAP, 75 SECURITY_EAP_SUITE_B, 76 }) 77 78 public @interface Security {} 79 80 public static final int SECURITY_NONE = 0; 81 public static final int SECURITY_WEP = 1; 82 public static final int SECURITY_PSK = 2; 83 public static final int SECURITY_EAP = 3; 84 public static final int SECURITY_OWE = 4; 85 public static final int SECURITY_SAE = 5; 86 public static final int SECURITY_EAP_SUITE_B = 6; 87 88 public static final int NUM_SECURITY_TYPES = 7; 89 90 @Retention(RetentionPolicy.SOURCE) 91 @IntDef(value = { 92 CONNECTED_STATE_DISCONNECTED, 93 CONNECTED_STATE_CONNECTED, 94 CONNECTED_STATE_CONNECTING 95 }) 96 97 public @interface ConnectedState {} 98 99 public static final int CONNECTED_STATE_DISCONNECTED = 0; 100 public static final int CONNECTED_STATE_CONNECTING = 1; 101 public static final int CONNECTED_STATE_CONNECTED = 2; 102 103 // Wi-Fi signal levels for displaying signal strength. 104 public static final int WIFI_LEVEL_MIN = 0; 105 public static final int WIFI_LEVEL_MAX = 4; 106 public static final int WIFI_LEVEL_UNREACHABLE = -1; 107 108 @Retention(RetentionPolicy.SOURCE) 109 @IntDef(value = { 110 SPEED_NONE, 111 SPEED_SLOW, 112 SPEED_MODERATE, 113 SPEED_FAST, 114 SPEED_VERY_FAST 115 }) 116 117 public @interface Speed {} 118 119 public static final int SPEED_NONE = 0; 120 public static final int SPEED_SLOW = 5; 121 public static final int SPEED_MODERATE = 10; 122 public static final int SPEED_FAST = 20; 123 public static final int SPEED_VERY_FAST = 30; 124 125 @Retention(RetentionPolicy.SOURCE) 126 @IntDef(value = { 127 METERED_CHOICE_AUTO, 128 METERED_CHOICE_METERED, 129 METERED_CHOICE_UNMETERED, 130 }) 131 132 public @interface MeteredChoice {} 133 134 // User's choice whether to treat a network as metered. 135 public static final int METERED_CHOICE_AUTO = 0; 136 public static final int METERED_CHOICE_METERED = 1; 137 public static final int METERED_CHOICE_UNMETERED = 2; 138 139 @Retention(RetentionPolicy.SOURCE) 140 @IntDef(value = { 141 PRIVACY_DEVICE_MAC, 142 PRIVACY_RANDOMIZED_MAC, 143 PRIVACY_UNKNOWN 144 }) 145 146 public @interface Privacy {} 147 148 public static final int PRIVACY_DEVICE_MAC = 0; 149 public static final int PRIVACY_RANDOMIZED_MAC = 1; 150 public static final int PRIVACY_UNKNOWN = 2; 151 152 @Retention(RetentionPolicy.SOURCE) 153 @IntDef(value = { 154 FREQUENCY_2_4_GHZ, 155 FREQUENCY_5_GHZ, 156 FREQUENCY_6_GHZ, 157 FREQUENCY_UNKNOWN 158 }) 159 160 public @interface Frequency {} 161 162 public static final int FREQUENCY_2_4_GHZ = 2_400; 163 public static final int FREQUENCY_5_GHZ = 5_000; 164 public static final int FREQUENCY_6_GHZ = 6_000; 165 public static final int FREQUENCY_UNKNOWN = -1; 166 167 /** 168 * Min bound on the 2.4 GHz (802.11b/g/n) WLAN channels. 169 */ 170 public static final int MIN_FREQ_24GHZ = 2400; 171 172 /** 173 * Max bound on the 2.4 GHz (802.11b/g/n) WLAN channels. 174 */ 175 public static final int MAX_FREQ_24GHZ = 2500; 176 177 /** 178 * Min bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels. 179 */ 180 public static final int MIN_FREQ_5GHZ = 4900; 181 182 /** 183 * Max bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels. 184 */ 185 public static final int MAX_FREQ_5GHZ = 5900; 186 187 /** 188 * Min bound on the 6.0 GHz (802.11ax) WLAN channels. 189 */ 190 public static final int MIN_FREQ_6GHZ = 5925; 191 192 /** 193 * Max bound on the 6.0 GHz (802.11ax) WLAN channels. 194 */ 195 public static final int MAX_FREQ_6GHZ = 7125; 196 197 /** 198 * Max ScanResult information displayed of Wi-Fi Verbose Logging. 199 */ 200 protected static final int MAX_VERBOSE_LOG_DISPLAY_SCANRESULT_COUNT = 4; 201 202 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 203 final boolean mForSavedNetworksPage; 204 205 protected final WifiManager mWifiManager; 206 207 // Callback associated with this WifiEntry. Subclasses should call its methods appropriately. 208 private WifiEntryCallback mListener; 209 protected Handler mCallbackHandler; 210 211 protected int mLevel = WIFI_LEVEL_UNREACHABLE; 212 protected int mSpeed = SPEED_NONE; 213 protected WifiInfo mWifiInfo; 214 protected NetworkInfo mNetworkInfo; 215 protected NetworkCapabilities mNetworkCapabilities; 216 protected ConnectedInfo mConnectedInfo; 217 protected WifiNetworkScoreCache mScoreCache; 218 219 protected ConnectCallback mConnectCallback; 220 protected DisconnectCallback mDisconnectCallback; 221 protected ForgetCallback mForgetCallback; 222 223 protected boolean mCalledConnect = false; 224 protected boolean mCalledDisconnect = false; 225 226 private boolean mIsValidated; 227 private boolean mIsDefaultNetwork; 228 protected boolean mIsLowQuality; 229 WifiEntry(@onNull Handler callbackHandler, @NonNull WifiManager wifiManager, @NonNull WifiNetworkScoreCache scoreCache, boolean forSavedNetworksPage)230 WifiEntry(@NonNull Handler callbackHandler, @NonNull WifiManager wifiManager, 231 @NonNull WifiNetworkScoreCache scoreCache, 232 boolean forSavedNetworksPage) throws IllegalArgumentException { 233 checkNotNull(callbackHandler, "Cannot construct with null handler!"); 234 checkNotNull(wifiManager, "Cannot construct with null WifiManager!"); 235 mCallbackHandler = callbackHandler; 236 mForSavedNetworksPage = forSavedNetworksPage; 237 mWifiManager = wifiManager; 238 mScoreCache = scoreCache; 239 } 240 241 // Info available for all WifiEntries // 242 243 /** The unique key defining a WifiEntry */ getKey()244 public abstract String getKey(); 245 246 /** Returns connection state of the network defined by the CONNECTED_STATE constants */ 247 @ConnectedState getConnectedState()248 public int getConnectedState() { 249 if (mNetworkInfo == null) { 250 return CONNECTED_STATE_DISCONNECTED; 251 } 252 253 switch (mNetworkInfo.getDetailedState()) { 254 case SCANNING: 255 case CONNECTING: 256 case AUTHENTICATING: 257 case OBTAINING_IPADDR: 258 case VERIFYING_POOR_LINK: 259 case CAPTIVE_PORTAL_CHECK: 260 return CONNECTED_STATE_CONNECTING; 261 case CONNECTED: 262 return CONNECTED_STATE_CONNECTED; 263 default: 264 return CONNECTED_STATE_DISCONNECTED; 265 } 266 } 267 268 269 /** Returns the display title. This is most commonly the SSID of a network. */ getTitle()270 public abstract String getTitle(); 271 272 /** Returns the display summary, it's a concise summary. */ getSummary()273 public String getSummary() { 274 return getSummary(true /* concise */); 275 } 276 277 /** Returns the second summary, it's for additional information of the WifiEntry */ getSecondSummary()278 public CharSequence getSecondSummary() { 279 return ""; 280 } 281 282 /** 283 * Returns the display summary. 284 * @param concise Whether to show more information. e.g., verbose logging. 285 */ getSummary(boolean concise)286 public abstract String getSummary(boolean concise); 287 288 /** 289 * Returns the signal strength level within [WIFI_LEVEL_MIN, WIFI_LEVEL_MAX]. 290 * A value of WIFI_LEVEL_UNREACHABLE indicates an out of range network. 291 */ getLevel()292 public int getLevel() { 293 return mLevel; 294 }; 295 296 /** 297 * Returns whether the level icon for this network should show an X or not. 298 */ shouldShowXLevelIcon()299 public boolean shouldShowXLevelIcon() { 300 return getConnectedState() != CONNECTED_STATE_DISCONNECTED 301 && (!mIsValidated || !mIsDefaultNetwork) && !canSignIn(); 302 } 303 304 /** Returns the speed value of the network defined by the SPEED constants */ 305 @Speed getSpeed()306 public int getSpeed() { 307 return mSpeed; 308 }; 309 310 /** 311 * Returns the SSID of the entry, if applicable. Null otherwise. 312 */ getSsid()313 public abstract String getSsid(); 314 315 /** Returns the security type defined by the SECURITY constants */ 316 @Security getSecurity()317 public abstract int getSecurity(); 318 319 /** Returns the MAC address of the connection */ getMacAddress()320 public abstract String getMacAddress(); 321 322 /** 323 * Indicates when a network is metered or the user marked the network as metered. 324 */ isMetered()325 public abstract boolean isMetered(); 326 327 /** 328 * Indicates whether or not an entry is for a saved configuration. 329 */ isSaved()330 public abstract boolean isSaved(); 331 332 /** 333 * Indicates whether or not an entry is for a saved configuration. 334 */ isSuggestion()335 public abstract boolean isSuggestion(); 336 337 /** 338 * Indicates whether or not an entry is for a subscription. 339 */ isSubscription()340 public abstract boolean isSubscription(); 341 342 /** 343 * Returns the WifiConfiguration of an entry or null if unavailable. This should be used when 344 * information on the WifiConfiguration needs to be modified and saved via 345 * {@link WifiManager#save(WifiConfiguration, WifiManager.ActionListener)}. 346 */ getWifiConfiguration()347 public abstract WifiConfiguration getWifiConfiguration(); 348 349 /** 350 * Returns the ConnectedInfo object pertaining to an active connection. 351 * 352 * Returns null if getConnectedState() != CONNECTED_STATE_CONNECTED. 353 */ getConnectedInfo()354 public ConnectedInfo getConnectedInfo() { 355 if (getConnectedState() != CONNECTED_STATE_CONNECTED) { 356 return null; 357 } 358 359 return mConnectedInfo; 360 } 361 362 /** 363 * Info associated with the active connection. 364 */ 365 public static class ConnectedInfo { 366 @Frequency 367 public int frequencyMhz; 368 public List<String> dnsServers = new ArrayList<>(); 369 public int linkSpeedMbps; 370 public String ipAddress; 371 public List<String> ipv6Addresses = new ArrayList<>(); 372 public String gateway; 373 public String subnetMask; 374 } 375 376 // User actions on a network 377 378 /** Returns whether the entry should show a connect option */ canConnect()379 public abstract boolean canConnect(); 380 /** Connects to the network */ connect(@ullable ConnectCallback callback)381 public abstract void connect(@Nullable ConnectCallback callback); 382 383 /** Returns whether the entry should show a disconnect option */ canDisconnect()384 public abstract boolean canDisconnect(); 385 /** Disconnects from the network */ disconnect(@ullable DisconnectCallback callback)386 public abstract void disconnect(@Nullable DisconnectCallback callback); 387 388 /** Returns whether the entry should show a forget option */ canForget()389 public abstract boolean canForget(); 390 /** Forgets the network */ forget(@ullable ForgetCallback callback)391 public abstract void forget(@Nullable ForgetCallback callback); 392 393 /** Returns whether the network can be signed-in to */ canSignIn()394 public abstract boolean canSignIn(); 395 /** Sign-in to the network. For captive portals. */ signIn(@ullable SignInCallback callback)396 public abstract void signIn(@Nullable SignInCallback callback); 397 398 /** Returns whether the network can be shared via QR code */ canShare()399 public abstract boolean canShare(); 400 /** Returns whether the user can use Easy Connect to onboard a device to the network */ canEasyConnect()401 public abstract boolean canEasyConnect(); 402 403 // Modifiable settings 404 405 /** 406 * Returns the user's choice whether to treat a network as metered, 407 * defined by the METERED_CHOICE constants 408 */ 409 @MeteredChoice getMeteredChoice()410 public abstract int getMeteredChoice(); 411 /** Returns whether the entry should let the user choose the metered treatment of a network */ canSetMeteredChoice()412 public abstract boolean canSetMeteredChoice(); 413 /** 414 * Sets the user's choice for treating a network as metered, 415 * defined by the METERED_CHOICE constants 416 */ setMeteredChoice(@eteredChoice int meteredChoice)417 public abstract void setMeteredChoice(@MeteredChoice int meteredChoice); 418 419 /** Returns whether the entry should let the user choose the MAC randomization setting */ canSetPrivacy()420 public abstract boolean canSetPrivacy(); 421 /** Returns the MAC randomization setting defined by the PRIVACY constants */ 422 @Privacy getPrivacy()423 public abstract int getPrivacy(); 424 /** Sets the user's choice for MAC randomization defined by the PRIVACY constants */ setPrivacy(@rivacy int privacy)425 public abstract void setPrivacy(@Privacy int privacy); 426 427 /** Returns whether the network has auto-join enabled */ isAutoJoinEnabled()428 public abstract boolean isAutoJoinEnabled(); 429 /** Returns whether the user can enable/disable auto-join */ canSetAutoJoinEnabled()430 public abstract boolean canSetAutoJoinEnabled(); 431 /** Sets whether a network will be auto-joined or not */ setAutoJoinEnabled(boolean enabled)432 public abstract void setAutoJoinEnabled(boolean enabled); 433 /** Returns the string displayed for @Security */ getSecurityString(boolean concise)434 public abstract String getSecurityString(boolean concise); 435 /** Returns whether subscription of the entry is expired */ isExpired()436 public abstract boolean isExpired(); 437 438 /** Returns whether a user can manage their subscription through this WifiEntry */ canManageSubscription()439 public boolean canManageSubscription() { 440 // Subclasses should implement this method. 441 return false; 442 }; 443 444 /** 445 * Return the URI string value of help, if it is not null, WifiPicker may show 446 * help icon and route the user to help page specified by the URI string. 447 * see {@link Intent#parseUri} 448 */ 449 @Nullable getHelpUriString()450 public String getHelpUriString() { 451 return null; 452 } 453 454 /** Allows the user to manage their subscription via an external flow */ manageSubscription()455 public void manageSubscription() { 456 // Subclasses should implement this method. 457 }; 458 459 /** Returns the ScanResult information of a WifiEntry */ getScanResultDescription()460 abstract String getScanResultDescription(); 461 462 /** Returns the network selection information of a WifiEntry */ getNetworkSelectionDescription()463 String getNetworkSelectionDescription() { 464 return ""; 465 } 466 467 /** Returns the network capability information of a WifiEntry */ getNetworkCapabilityDescription()468 String getNetworkCapabilityDescription() { 469 final StringBuilder sb = new StringBuilder(); 470 if (getConnectedState() == CONNECTED_STATE_CONNECTED) { 471 sb.append("isValidated:") 472 .append(mIsValidated) 473 .append(", isDefaultNetwork:") 474 .append(mIsDefaultNetwork) 475 .append(", isLowQuality:") 476 .append(mIsLowQuality); 477 } 478 return sb.toString(); 479 } 480 481 /** 482 * In Wi-Fi picker, when users click a saved network, it will connect to the Wi-Fi network. 483 * However, for some special cases, Wi-Fi picker should show Wi-Fi editor UI for users to edit 484 * security or password before connecting. Or users will always get connection fail results. 485 */ shouldEditBeforeConnect()486 public boolean shouldEditBeforeConnect() { 487 return false; 488 } 489 490 /** 491 * Sets the callback listener for WifiEntryCallback methods. 492 * Subsequent calls will overwrite the previous listener. 493 */ setListener(WifiEntryCallback listener)494 public void setListener(WifiEntryCallback listener) { 495 mListener = listener; 496 } 497 498 /** 499 * Listener for changes to the state of the WifiEntry. 500 * This callback will be invoked on the main thread. 501 */ 502 public interface WifiEntryCallback { 503 /** 504 * Indicates the state of the WifiEntry has changed and clients may retrieve updates through 505 * the WifiEntry getter methods. 506 */ 507 @MainThread onUpdated()508 void onUpdated(); 509 } 510 511 @AnyThread notifyOnUpdated()512 protected void notifyOnUpdated() { 513 if (mListener != null) { 514 mCallbackHandler.post(() -> mListener.onUpdated()); 515 } 516 } 517 518 /** 519 * Listener for changes to the state of the WifiEntry. 520 * This callback will be invoked on the main thread. 521 */ 522 public interface ConnectCallback { 523 @Retention(RetentionPolicy.SOURCE) 524 @IntDef(value = { 525 CONNECT_STATUS_SUCCESS, 526 CONNECT_STATUS_FAILURE_NO_CONFIG, 527 CONNECT_STATUS_FAILURE_UNKNOWN 528 }) 529 530 public @interface ConnectStatus {} 531 532 int CONNECT_STATUS_SUCCESS = 0; 533 int CONNECT_STATUS_FAILURE_NO_CONFIG = 1; 534 int CONNECT_STATUS_FAILURE_UNKNOWN = 2; 535 536 /** 537 * Result of the connect request indicated by the CONNECT_STATUS constants. 538 */ 539 @MainThread onConnectResult(@onnectStatus int status)540 void onConnectResult(@ConnectStatus int status); 541 } 542 543 /** 544 * Listener for changes to the state of the WifiEntry. 545 * This callback will be invoked on the main thread. 546 */ 547 public interface DisconnectCallback { 548 @Retention(RetentionPolicy.SOURCE) 549 @IntDef(value = { 550 DISCONNECT_STATUS_SUCCESS, 551 DISCONNECT_STATUS_FAILURE_UNKNOWN 552 }) 553 554 public @interface DisconnectStatus {} 555 556 int DISCONNECT_STATUS_SUCCESS = 0; 557 int DISCONNECT_STATUS_FAILURE_UNKNOWN = 1; 558 /** 559 * Result of the disconnect request indicated by the DISCONNECT_STATUS constants. 560 */ 561 @MainThread onDisconnectResult(@isconnectStatus int status)562 void onDisconnectResult(@DisconnectStatus int status); 563 } 564 565 /** 566 * Listener for changes to the state of the WifiEntry. 567 * This callback will be invoked on the main thread. 568 */ 569 public interface ForgetCallback { 570 @Retention(RetentionPolicy.SOURCE) 571 @IntDef(value = { 572 FORGET_STATUS_SUCCESS, 573 FORGET_STATUS_FAILURE_UNKNOWN 574 }) 575 576 public @interface ForgetStatus {} 577 578 int FORGET_STATUS_SUCCESS = 0; 579 int FORGET_STATUS_FAILURE_UNKNOWN = 1; 580 581 /** 582 * Result of the forget request indicated by the FORGET_STATUS constants. 583 */ 584 @MainThread onForgetResult(@orgetStatus int status)585 void onForgetResult(@ForgetStatus int status); 586 } 587 588 /** 589 * Listener for changes to the state of the WifiEntry. 590 * This callback will be invoked on the main thread. 591 */ 592 public interface SignInCallback { 593 @Retention(RetentionPolicy.SOURCE) 594 @IntDef(value = { 595 SIGNIN_STATUS_SUCCESS, 596 SIGNIN_STATUS_FAILURE_UNKNOWN 597 }) 598 599 public @interface SignInStatus {} 600 601 int SIGNIN_STATUS_SUCCESS = 0; 602 int SIGNIN_STATUS_FAILURE_UNKNOWN = 1; 603 604 /** 605 * Result of the sign-in request indicated by the SIGNIN_STATUS constants. 606 */ 607 @MainThread onSignInResult(@ignInStatus int status)608 void onSignInResult(@SignInStatus int status); 609 } 610 611 /** 612 * Returns whether or not the supplied WifiInfo and NetworkInfo represent this WifiEntry 613 */ connectionInfoMatches(@onNull WifiInfo wifiInfo, @NonNull NetworkInfo networkInfo)614 protected abstract boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo, 615 @NonNull NetworkInfo networkInfo); 616 617 /** 618 * Updates information regarding the current network connection. If the supplied WifiInfo and 619 * NetworkInfo do not match this WifiEntry, then the WifiEntry will update to be 620 * unconnected. 621 */ 622 @WorkerThread updateConnectionInfo(@ullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo)623 void updateConnectionInfo(@Nullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo) { 624 if (wifiInfo != null && networkInfo != null 625 && connectionInfoMatches(wifiInfo, networkInfo)) { 626 // Connection info matches, so the WifiInfo/NetworkInfo represent this network and 627 // the network is currently connecting or connected. 628 mWifiInfo = wifiInfo; 629 mNetworkInfo = networkInfo; 630 final int wifiInfoRssi = wifiInfo.getRssi(); 631 if (wifiInfoRssi != INVALID_RSSI) { 632 mLevel = mWifiManager.calculateSignalLevel(wifiInfoRssi); 633 mSpeed = getSpeedFromWifiInfo(mScoreCache, wifiInfo); 634 } 635 if (getConnectedState() == CONNECTED_STATE_CONNECTED) { 636 if (mCalledConnect) { 637 mCalledConnect = false; 638 mCallbackHandler.post(() -> { 639 if (mConnectCallback != null) { 640 mConnectCallback.onConnectResult( 641 ConnectCallback.CONNECT_STATUS_SUCCESS); 642 } 643 }); 644 } 645 646 if (mConnectedInfo == null) { 647 mConnectedInfo = new ConnectedInfo(); 648 } 649 mConnectedInfo.frequencyMhz = wifiInfo.getFrequency(); 650 mConnectedInfo.linkSpeedMbps = wifiInfo.getLinkSpeed(); 651 } 652 } else { // Connection info doesn't matched, so this network is disconnected 653 mNetworkInfo = null; 654 mNetworkCapabilities = null; 655 mConnectedInfo = null; 656 mIsValidated = false; 657 mIsDefaultNetwork = false; 658 mIsLowQuality = false; 659 if (mCalledDisconnect) { 660 mCalledDisconnect = false; 661 mCallbackHandler.post(() -> { 662 if (mDisconnectCallback != null) { 663 mDisconnectCallback.onDisconnectResult( 664 DisconnectCallback.DISCONNECT_STATUS_SUCCESS); 665 } 666 }); 667 } 668 } 669 notifyOnUpdated(); 670 } 671 672 // Method for WifiTracker to update the link properties, which is valid for all WifiEntry types. 673 @WorkerThread updateLinkProperties(@ullable LinkProperties linkProperties)674 void updateLinkProperties(@Nullable LinkProperties linkProperties) { 675 if (linkProperties == null || getConnectedState() != CONNECTED_STATE_CONNECTED) { 676 mConnectedInfo = null; 677 notifyOnUpdated(); 678 return; 679 } 680 681 if (mConnectedInfo == null) { 682 mConnectedInfo = new ConnectedInfo(); 683 } 684 // Find IPv4 and IPv6 addresses, and subnet mask 685 List<String> ipv6Addresses = new ArrayList<>(); 686 for (LinkAddress addr : linkProperties.getLinkAddresses()) { 687 if (addr.getAddress() instanceof Inet4Address) { 688 mConnectedInfo.ipAddress = addr.getAddress().getHostAddress(); 689 try { 690 InetAddress all = InetAddress.getByAddress( 691 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255}); 692 mConnectedInfo.subnetMask = NetworkUtils.getNetworkPart( 693 all, addr.getPrefixLength()).getHostAddress(); 694 } catch (UnknownHostException e) { 695 // Leave subnet null; 696 } 697 } else if (addr.getAddress() instanceof Inet6Address) { 698 ipv6Addresses.add(addr.getAddress().getHostAddress()); 699 } 700 } 701 mConnectedInfo.ipv6Addresses = ipv6Addresses; 702 703 // Find IPv4 default gateway. 704 for (RouteInfo routeInfo : linkProperties.getRoutes()) { 705 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) { 706 mConnectedInfo.gateway = routeInfo.getGateway().getHostAddress(); 707 break; 708 } 709 } 710 711 // Find DNS servers 712 mConnectedInfo.dnsServers = linkProperties.getDnsServers().stream() 713 .map(InetAddress::getHostAddress).collect(Collectors.toList()); 714 715 notifyOnUpdated(); 716 } 717 718 @WorkerThread setIsDefaultNetwork(boolean isDefaultNetwork)719 void setIsDefaultNetwork(boolean isDefaultNetwork) { 720 mIsDefaultNetwork = isDefaultNetwork; 721 notifyOnUpdated(); 722 } 723 724 @WorkerThread setIsLowQuality(boolean isLowQuality)725 void setIsLowQuality(boolean isLowQuality) { 726 mIsLowQuality = isLowQuality; 727 } 728 729 // Method for WifiTracker to update a connected WifiEntry's network capabilities. 730 @WorkerThread updateNetworkCapabilities(@ullable NetworkCapabilities capabilities)731 void updateNetworkCapabilities(@Nullable NetworkCapabilities capabilities) { 732 mNetworkCapabilities = capabilities; 733 if (mConnectedInfo == null) { 734 return; 735 } 736 mIsValidated = mNetworkCapabilities != null 737 && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); 738 notifyOnUpdated(); 739 } 740 getWifiInfoDescription()741 String getWifiInfoDescription() { 742 final StringJoiner sj = new StringJoiner(" "); 743 if (getConnectedState() == CONNECTED_STATE_CONNECTED && mWifiInfo != null) { 744 sj.add("f = " + mWifiInfo.getFrequency()); 745 final String bssid = mWifiInfo.getBSSID(); 746 if (bssid != null) { 747 sj.add(bssid); 748 } 749 sj.add("standard = " + mWifiInfo.getWifiStandard()); 750 sj.add("rssi = " + mWifiInfo.getRssi()); 751 sj.add("score = " + mWifiInfo.getScore()); 752 sj.add(String.format(" tx=%.1f,", mWifiInfo.getSuccessfulTxPacketsPerSecond())); 753 sj.add(String.format("%.1f,", mWifiInfo.getRetriedTxPacketsPerSecond())); 754 sj.add(String.format("%.1f ", mWifiInfo.getLostTxPacketsPerSecond())); 755 sj.add(String.format("rx=%.1f", mWifiInfo.getSuccessfulRxPacketsPerSecond())); 756 } 757 return sj.toString(); 758 } 759 760 protected class ConnectActionListener implements WifiManager.ActionListener { 761 @Override onSuccess()762 public void onSuccess() { 763 mCalledConnect = true; 764 // If we aren't connected to the network after 10 seconds, trigger the failure callback 765 mCallbackHandler.postDelayed(() -> { 766 if (mConnectCallback != null && mCalledConnect 767 && getConnectedState() == CONNECTED_STATE_DISCONNECTED) { 768 mConnectCallback.onConnectResult( 769 ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN); 770 mCalledConnect = false; 771 } 772 }, 10_000 /* delayMillis */); 773 } 774 775 @Override onFailure(int i)776 public void onFailure(int i) { 777 mCallbackHandler.post(() -> { 778 if (mConnectCallback != null) { 779 mConnectCallback.onConnectResult( 780 mConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN); 781 } 782 }); 783 } 784 } 785 786 protected class ForgetActionListener implements WifiManager.ActionListener { 787 @Override onSuccess()788 public void onSuccess() { 789 mCallbackHandler.post(() -> { 790 if (mForgetCallback != null) { 791 mForgetCallback.onForgetResult(ForgetCallback.FORGET_STATUS_SUCCESS); 792 } 793 }); 794 } 795 796 @Override onFailure(int i)797 public void onFailure(int i) { 798 mCallbackHandler.post(() -> { 799 if (mForgetCallback != null) { 800 mForgetCallback.onForgetResult(ForgetCallback.FORGET_STATUS_FAILURE_UNKNOWN); 801 } 802 }); 803 } 804 } 805 806 @Override compareTo(@onNull WifiEntry other)807 public int compareTo(@NonNull WifiEntry other) { 808 if (getLevel() != WIFI_LEVEL_UNREACHABLE && other.getLevel() == WIFI_LEVEL_UNREACHABLE) { 809 return -1; 810 } 811 if (getLevel() == WIFI_LEVEL_UNREACHABLE && other.getLevel() != WIFI_LEVEL_UNREACHABLE) { 812 return 1; 813 } 814 815 if (isSubscription() && !other.isSubscription()) return -1; 816 if (!isSubscription() && other.isSubscription()) return 1; 817 818 if (isSaved() && !other.isSaved()) return -1; 819 if (!isSaved() && other.isSaved()) return 1; 820 821 if (isSuggestion() && !other.isSuggestion()) return -1; 822 if (!isSuggestion() && other.isSuggestion()) return 1; 823 824 if (getLevel() > other.getLevel()) return -1; 825 if (getLevel() < other.getLevel()) return 1; 826 827 return getTitle().compareTo(other.getTitle()); 828 } 829 830 @Override equals(Object other)831 public boolean equals(Object other) { 832 if (!(other instanceof WifiEntry)) return false; 833 return getKey().equals(((WifiEntry) other).getKey()); 834 } 835 836 @Override toString()837 public String toString() { 838 return new StringBuilder() 839 .append(getKey()) 840 .append(",title:") 841 .append(getTitle()) 842 .append(",summary:") 843 .append(getSummary()) 844 .append(",isSaved:") 845 .append(isSaved()) 846 .append(",isSubscription:") 847 .append(isSubscription()) 848 .append(",isSuggestion:") 849 .append(isSuggestion()) 850 .append(",level:") 851 .append(getLevel()) 852 .append(shouldShowXLevelIcon() ? "X" : "") 853 .append(",security:") 854 .append(getSecurity()) 855 .append(",connected:") 856 .append(getConnectedState() == CONNECTED_STATE_CONNECTED ? "true" : "false") 857 .append(",connectedInfo:") 858 .append(getConnectedInfo()) 859 .append(",isValidated:") 860 .append(mIsValidated) 861 .append(",isDefaultNetwork:") 862 .append(mIsDefaultNetwork) 863 .toString(); 864 } 865 } 866