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