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 Network mDefaultNetwork;
248     protected NetworkCapabilities mDefaultNetworkCapabilities;
249     protected ConnectivityDiagnosticsManager.ConnectivityReport mConnectivityReport;
250     protected ConnectedInfo mConnectedInfo;
251 
252     protected ConnectCallback mConnectCallback;
253     protected DisconnectCallback mDisconnectCallback;
254     protected ForgetCallback mForgetCallback;
255 
256     protected boolean mCalledConnect = false;
257     protected boolean mCalledDisconnect = false;
258 
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 synchronized 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         if (mNetwork != null && mNetwork.equals(mDefaultNetwork)) {
372             return true;
373         }
374 
375         // Try to get a WifiInfo from the default network capabilities in case it's a
376         // VcnTransportInfo with an underlying WifiInfo.
377         if (mDefaultNetworkCapabilities == null) {
378             return false;
379         }
380         WifiInfo defaultWifiInfo = Utils.getWifiInfo(mDefaultNetworkCapabilities);
381         if (defaultWifiInfo != null) {
382             return connectionInfoMatches(defaultWifiInfo);
383         }
384 
385         // Match based on the underlying networks if there are any (e.g. VPN).
386         List<Network> underlyingNetworks = BuildCompat.isAtLeastT()
387                 ? mDefaultNetworkCapabilities.getUnderlyingNetworks() : null;
388         return underlyingNetworks != null && underlyingNetworks.contains(mNetwork);
389     }
390 
391     /**
392      * Returns whether this network is the primary Wi-Fi network or not.
393      */
isPrimaryNetwork()394     public synchronized boolean isPrimaryNetwork() {
395         if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) {
396             // In case we have mNetworkInfo but the state is disconnected.
397             return false;
398         }
399         return mNetworkInfo != null
400                 || (mWifiInfo != null && NonSdkApiWrapper.isPrimary(mWifiInfo));
401     }
402 
403     /**
404      * Returns whether this network is considered low quality.
405      */
isLowQuality()406     public synchronized boolean isLowQuality() {
407         return isPrimaryNetwork() && hasInternetAccess() && !isDefaultNetwork()
408                 && mDefaultNetworkCapabilities != null
409                 && mDefaultNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
410                 && !mDefaultNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
411                 && mDefaultNetworkCapabilities.hasCapability(
412                         NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
413     }
414 
415     /**
416      * Returns whether this network should display its SSID separately from the title
417      * (e.g. the Network Details page), for networks whose display titles differ from the SSID.
418      */
shouldShowSsid()419     public boolean shouldShowSsid() {
420         return false;
421     }
422 
423     /**
424      * Returns the SSID of the entry, if applicable. Null otherwise.
425      */
426     @Nullable
getSsid()427     public String getSsid() {
428         return null;
429     }
430 
431     /**
432      * Returns the security type defined by the SECURITY constants
433      * @deprecated Use getSecurityTypes() which can return multiple security types.
434      */
435     // TODO(b/187554920): Remove this and move all clients to getSecurityTypes()
436     @Deprecated
437     @Security
getSecurity()438     public int getSecurity() {
439         switch (getSingleSecurityTypeFromMultipleSecurityTypes(getSecurityTypes())) {
440             case WifiInfo.SECURITY_TYPE_OPEN:
441                 return SECURITY_NONE;
442             case WifiInfo.SECURITY_TYPE_OWE:
443                 return SECURITY_OWE;
444             case WifiInfo.SECURITY_TYPE_WEP:
445                 return SECURITY_WEP;
446             case WifiInfo.SECURITY_TYPE_PSK:
447                 return SECURITY_PSK;
448             case WifiInfo.SECURITY_TYPE_SAE:
449                 return SECURITY_SAE;
450             case WifiInfo.SECURITY_TYPE_EAP:
451                 return SECURITY_EAP;
452             case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
453                 return SECURITY_EAP_WPA3_ENTERPRISE;
454             case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
455                 return SECURITY_EAP_SUITE_B;
456             case WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2:
457             case WifiInfo.SECURITY_TYPE_PASSPOINT_R3:
458                 return SECURITY_EAP;
459             default:
460                 return SECURITY_NONE;
461         }
462     }
463 
464     /**
465      * Returns security type of the current connection, or the available types for connection
466      * in the form of the SECURITY_TYPE_* values in {@link WifiInfo}
467      */
468     @NonNull
getSecurityTypes()469     public List<Integer> getSecurityTypes() {
470         return Collections.emptyList();
471     }
472 
473     /** Returns the MAC address of the connection */
474     @Nullable
getMacAddress()475     public String getMacAddress() {
476         return null;
477     }
478 
479     /**
480      * Indicates when a network is metered or the user marked the network as metered.
481      */
isMetered()482     public boolean isMetered() {
483         return false;
484     }
485 
486     /**
487      * Indicates whether or not an entry is for a saved configuration.
488      */
isSaved()489     public boolean isSaved() {
490         return false;
491     }
492 
493     /**
494      * Indicates whether or not an entry is for a saved configuration.
495      */
isSuggestion()496     public boolean isSuggestion() {
497         return false;
498     }
499 
500     /**
501      * Indicates whether or not an entry is for a subscription.
502      */
isSubscription()503     public boolean isSubscription() {
504         return false;
505     }
506 
507     /**
508      * Returns whether this entry needs to be configured with a new WifiConfiguration before
509      * connection.
510      */
needsWifiConfiguration()511     public boolean needsWifiConfiguration() {
512         return false;
513     }
514 
515     /**
516      * Returns the WifiConfiguration of an entry or null if unavailable. This should be used when
517      * information on the WifiConfiguration needs to be modified and saved via
518      * {@link WifiManager#save(WifiConfiguration, WifiManager.ActionListener)}.
519      */
520     @Nullable
getWifiConfiguration()521     public WifiConfiguration getWifiConfiguration() {
522         return null;
523     }
524 
525     /**
526      * Returns the ConnectedInfo object pertaining to an active connection.
527      *
528      * Returns null if getConnectedState() != CONNECTED_STATE_CONNECTED.
529      */
530     @Nullable
getConnectedInfo()531     public synchronized ConnectedInfo getConnectedInfo() {
532         if (getConnectedState() != CONNECTED_STATE_CONNECTED) {
533             return null;
534         }
535 
536         return new ConnectedInfo(mConnectedInfo);
537     }
538 
539     /**
540      * Info associated with the active connection.
541      */
542     public static class ConnectedInfo {
543         @Frequency
544         public int frequencyMhz;
545         public List<String> dnsServers = new ArrayList<>();
546         public int linkSpeedMbps;
547         public String ipAddress;
548         public List<String> ipv6Addresses = new ArrayList<>();
549         public String gateway;
550         public String subnetMask;
551         public int wifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN;
552         public NetworkCapabilities networkCapabilities;
553 
554         /**
555          * Creates an empty ConnectedInfo
556          */
ConnectedInfo()557         public ConnectedInfo() {
558         }
559 
560         /**
561          * Creates a ConnectedInfo with all fields copied from an input ConnectedInfo
562          */
ConnectedInfo(@onNull ConnectedInfo other)563         public ConnectedInfo(@NonNull ConnectedInfo other) {
564             frequencyMhz = other.frequencyMhz;
565             dnsServers = new ArrayList<>(dnsServers);
566             linkSpeedMbps = other.linkSpeedMbps;
567             ipAddress = other.ipAddress;
568             ipv6Addresses = new ArrayList<>(other.ipv6Addresses);
569             gateway = other.gateway;
570             subnetMask = other.subnetMask;
571             wifiStandard = other.wifiStandard;
572             networkCapabilities = other.networkCapabilities;
573         }
574     }
575 
576     // User actions on a network
577 
578     /** Returns whether the entry should show a connect option */
canConnect()579     public boolean canConnect() {
580         return false;
581     }
582 
583     /** Connects to the network */
connect(@ullable ConnectCallback callback)584     public void connect(@Nullable ConnectCallback callback) {
585         // Do nothing.
586     }
587 
588     /** Returns whether the entry should show a disconnect option */
canDisconnect()589     public boolean canDisconnect() {
590         return false;
591     }
592 
593     /** Disconnects from the network */
disconnect(@ullable DisconnectCallback callback)594     public void disconnect(@Nullable DisconnectCallback callback) {
595         // Do nothing.
596     }
597 
598     /** Returns whether the entry should show a forget option */
canForget()599     public boolean canForget() {
600         return false;
601     }
602 
603     /** Forgets the network */
forget(@ullable ForgetCallback callback)604     public void forget(@Nullable ForgetCallback callback) {
605         // Do nothing.
606     }
607 
608     /** Returns whether the network can be signed-in to */
canSignIn()609     public boolean canSignIn() {
610         return false;
611     }
612 
613     /** Sign-in to the network. For captive portals. */
signIn(@ullable SignInCallback callback)614     public void signIn(@Nullable SignInCallback callback) {
615         // Do nothing.
616     }
617 
618     /** Returns whether the network can be shared via QR code */
canShare()619     public boolean canShare() {
620         return false;
621     }
622 
623     /** Returns whether the user can use Easy Connect to onboard a device to the network */
canEasyConnect()624     public boolean canEasyConnect() {
625         return false;
626     }
627 
628     // Modifiable settings
629 
630     /**
631      *  Returns the user's choice whether to treat a network as metered,
632      *  defined by the METERED_CHOICE constants
633      */
634     @MeteredChoice
getMeteredChoice()635     public int getMeteredChoice() {
636         return METERED_CHOICE_AUTO;
637     }
638 
639     /** Returns whether the entry should let the user choose the metered treatment of a network */
canSetMeteredChoice()640     public boolean canSetMeteredChoice() {
641         return false;
642     }
643 
644     /**
645      * Sets the user's choice for treating a network as metered,
646      * defined by the METERED_CHOICE constants
647      */
setMeteredChoice(@eteredChoice int meteredChoice)648     public void setMeteredChoice(@MeteredChoice int meteredChoice) {
649         // Do nothing.
650     }
651 
652     /** Returns whether the entry should let the user choose the MAC randomization setting */
canSetPrivacy()653     public boolean canSetPrivacy() {
654         return false;
655     }
656 
657     /** Returns the MAC randomization setting defined by the PRIVACY constants */
658     @Privacy
getPrivacy()659     public int getPrivacy() {
660         return PRIVACY_UNKNOWN;
661     }
662 
663     /** Sets the user's choice for MAC randomization defined by the PRIVACY constants */
setPrivacy(@rivacy int privacy)664     public void setPrivacy(@Privacy int privacy) {
665         // Do nothing.
666     }
667 
668     /** Returns whether the network has auto-join enabled */
isAutoJoinEnabled()669     public boolean isAutoJoinEnabled() {
670         return false;
671     }
672 
673     /** Returns whether the user can enable/disable auto-join */
canSetAutoJoinEnabled()674     public boolean canSetAutoJoinEnabled() {
675         return false;
676     }
677 
678     /** Sets whether a network will be auto-joined or not */
setAutoJoinEnabled(boolean enabled)679     public void setAutoJoinEnabled(boolean enabled) {
680         // Do nothing.
681     }
682 
683     /** Returns the string displayed for @Security */
getSecurityString(boolean concise)684     public String getSecurityString(boolean concise) {
685         return "";
686     }
687 
688     /** Returns the string displayed for the Wi-Fi standard */
getStandardString()689     public String getStandardString() {
690         return "";
691     }
692 
693     /**
694      * Info associated with the certificate based enterprise connection
695      */
696     public static class CertificateInfo {
697         /**
698          * Server certificate validation method. Used to show the security certificate strings in
699          * the Network Details page.
700          */
701         @Retention(RetentionPolicy.SOURCE)
702         @IntDef(value = {
703                 CERTIFICATE_VALIDATION_METHOD_USING_NONE,
704                 CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA,
705                 CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE,
706                 CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING,
707         })
708 
709         public @interface CertificateValidationMethod {}
710         public static final int CERTIFICATE_VALIDATION_METHOD_USING_NONE = 0;
711         public static final int CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA = 1;
712         public static final int CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE = 2;
713         public static final int CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING = 3;
714 
715         public @CertificateValidationMethod int validationMethod;
716 
717         /** Non null only for  CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA */
718         @Nullable public String[] caCertificateAliases;
719 
720         /** Domain name / server name */
721         @Nullable public String domain;
722     }
723 
724     /**
725      * Returns the CertificateInfo to display, or null if it is not a certificate based connection.
726      */
727     @Nullable
getCertificateInfo()728     public CertificateInfo getCertificateInfo() {
729         return null;
730     }
731 
732     /** Returns the string displayed for the Wi-Fi band */
getBandString()733     public String getBandString() {
734         return "";
735     }
736 
737     /**
738      * Returns the string displayed for Tx link speed.
739      */
getTxSpeedString()740     public String getTxSpeedString() {
741         return Utils.getSpeedString(mContext, mWifiInfo, /* isTx */ true);
742     }
743 
744     /**
745      * Returns the string displayed for Rx link speed.
746      */
getRxSpeedString()747     public String getRxSpeedString() {
748         return Utils.getSpeedString(mContext, mWifiInfo, /* isTx */ false);
749     }
750 
751     /** Returns whether subscription of the entry is expired */
isExpired()752     public boolean isExpired() {
753         return false;
754     }
755 
756 
757     /** Returns whether a user can manage their subscription through this WifiEntry */
canManageSubscription()758     public boolean canManageSubscription() {
759         return mManageSubscriptionAction.isPresent();
760     };
761 
762     /**
763      * Return the URI string value of help, if it is not null, WifiPicker may show
764      * help icon and route the user to help page specified by the URI string.
765      * see {@link Intent#parseUri}
766      */
767     @Nullable
getHelpUriString()768     public String getHelpUriString() {
769         return null;
770     }
771 
772     /** Allows the user to manage their subscription via an external flow */
manageSubscription()773     public void manageSubscription() {
774         mManageSubscriptionAction.ifPresent(ManageSubscriptionAction::onExecute);
775     };
776 
777     /** Set the action to be called on calling WifiEntry#manageSubscription. */
setManageSubscriptionAction( @onNull ManageSubscriptionAction manageSubscriptionAction)778     public void setManageSubscriptionAction(
779             @NonNull ManageSubscriptionAction manageSubscriptionAction) {
780         // only notify update on 1st time
781         boolean notify = !mManageSubscriptionAction.isPresent();
782 
783         mManageSubscriptionAction = Optional.of(manageSubscriptionAction);
784         if (notify) {
785             notifyOnUpdated();
786         }
787     }
788 
789     /** Returns the ScanResult information of a WifiEntry */
790     @NonNull
getScanResultDescription()791     protected String getScanResultDescription() {
792         return "";
793     }
794 
795     /** Returns the network selection information of a WifiEntry */
796     @NonNull
getNetworkSelectionDescription()797     String getNetworkSelectionDescription() {
798         return "";
799     }
800 
801     /** Returns the network capability information of a WifiEntry */
802     @NonNull
getNetworkCapabilityDescription()803     String getNetworkCapabilityDescription() {
804         final StringBuilder sb = new StringBuilder();
805         if (getConnectedState() == CONNECTED_STATE_CONNECTED) {
806             sb.append("hasInternet:")
807                     .append(hasInternetAccess())
808                     .append(", isDefaultNetwork:")
809                     .append(isDefaultNetwork())
810                     .append(", isLowQuality:")
811                     .append(isLowQuality());
812         }
813         return sb.toString();
814     }
815 
816     /**
817      * In Wi-Fi picker, when users click a saved network, it will connect to the Wi-Fi network.
818      * However, for some special cases, Wi-Fi picker should show Wi-Fi editor UI for users to edit
819      * security or password before connecting. Or users will always get connection fail results.
820      */
shouldEditBeforeConnect()821     public boolean shouldEditBeforeConnect() {
822         return false;
823     }
824 
825     /**
826      * Whether there are admin restrictions preventing connection to this network.
827      */
hasAdminRestrictions()828     public boolean hasAdminRestrictions() {
829         return false;
830     }
831 
832     /**
833      * Sets the callback listener for WifiEntryCallback methods.
834      * Subsequent calls will overwrite the previous listener.
835      */
setListener(WifiEntryCallback listener)836     public synchronized void setListener(WifiEntryCallback listener) {
837         mListener = listener;
838     }
839 
840     /**
841      * Listener for changes to the state of the WifiEntry.
842      * This callback will be invoked on the main thread.
843      */
844     public interface WifiEntryCallback {
845         /**
846          * Indicates the state of the WifiEntry has changed and clients may retrieve updates through
847          * the WifiEntry getter methods.
848          */
849         @MainThread
onUpdated()850         void onUpdated();
851     }
852 
853     @AnyThread
notifyOnUpdated()854     protected void notifyOnUpdated() {
855         if (mListener != null) {
856             mCallbackHandler.post(() -> {
857                 final WifiEntryCallback listener = mListener;
858                 if (listener != null) {
859                     listener.onUpdated();
860                 }
861             });
862         }
863     }
864 
865     /**
866      * Listener for changes to the state of the WifiEntry.
867      * This callback will be invoked on the main thread.
868      */
869     public interface ConnectCallback {
870         @Retention(RetentionPolicy.SOURCE)
871         @IntDef(value = {
872                 CONNECT_STATUS_SUCCESS,
873                 CONNECT_STATUS_FAILURE_NO_CONFIG,
874                 CONNECT_STATUS_FAILURE_UNKNOWN,
875                 CONNECT_STATUS_FAILURE_SIM_ABSENT
876         })
877 
878         public @interface ConnectStatus {}
879 
880         int CONNECT_STATUS_SUCCESS = 0;
881         int CONNECT_STATUS_FAILURE_NO_CONFIG = 1;
882         int CONNECT_STATUS_FAILURE_UNKNOWN = 2;
883         int CONNECT_STATUS_FAILURE_SIM_ABSENT = 3;
884 
885         /**
886          * Result of the connect request indicated by the CONNECT_STATUS constants.
887          */
888         @MainThread
onConnectResult(@onnectStatus int status)889         void onConnectResult(@ConnectStatus int status);
890     }
891 
892     /**
893      * Listener for changes to the state of the WifiEntry.
894      * This callback will be invoked on the main thread.
895      */
896     public interface DisconnectCallback {
897         @Retention(RetentionPolicy.SOURCE)
898         @IntDef(value = {
899                 DISCONNECT_STATUS_SUCCESS,
900                 DISCONNECT_STATUS_FAILURE_UNKNOWN
901         })
902 
903         public @interface DisconnectStatus {}
904 
905         int DISCONNECT_STATUS_SUCCESS = 0;
906         int DISCONNECT_STATUS_FAILURE_UNKNOWN = 1;
907         /**
908          * Result of the disconnect request indicated by the DISCONNECT_STATUS constants.
909          */
910         @MainThread
onDisconnectResult(@isconnectStatus int status)911         void onDisconnectResult(@DisconnectStatus int status);
912     }
913 
914     /**
915      * Listener for changes to the state of the WifiEntry.
916      * This callback will be invoked on the main thread.
917      */
918     public interface ForgetCallback {
919         @Retention(RetentionPolicy.SOURCE)
920         @IntDef(value = {
921                 FORGET_STATUS_SUCCESS,
922                 FORGET_STATUS_FAILURE_UNKNOWN
923         })
924 
925         public @interface ForgetStatus {}
926 
927         int FORGET_STATUS_SUCCESS = 0;
928         int FORGET_STATUS_FAILURE_UNKNOWN = 1;
929 
930         /**
931          * Result of the forget request indicated by the FORGET_STATUS constants.
932          */
933         @MainThread
onForgetResult(@orgetStatus int status)934         void onForgetResult(@ForgetStatus int status);
935     }
936 
937     /**
938      * Listener for changes to the state of the WifiEntry.
939      * This callback will be invoked on the main thread.
940      */
941     public interface SignInCallback {
942         @Retention(RetentionPolicy.SOURCE)
943         @IntDef(value = {
944                 SIGNIN_STATUS_SUCCESS,
945                 SIGNIN_STATUS_FAILURE_UNKNOWN
946         })
947 
948         public @interface SignInStatus {}
949 
950         int SIGNIN_STATUS_SUCCESS = 0;
951         int SIGNIN_STATUS_FAILURE_UNKNOWN = 1;
952 
953         /**
954          * Result of the sign-in request indicated by the SIGNIN_STATUS constants.
955          */
956         @MainThread
onSignInResult(@ignInStatus int status)957         void onSignInResult(@SignInStatus int status);
958     }
959 
960     /**
961      * Returns whether the supplied WifiInfo represents this WifiEntry
962      */
connectionInfoMatches(@onNull WifiInfo wifiInfo)963     protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo) {
964         return false;
965     }
966 
967     /**
968      * Updates this WifiEntry with the given primary WifiInfo/NetworkInfo if they match.
969      * @param primaryWifiInfo Primary WifiInfo that has changed
970      * @param networkInfo NetworkInfo of the primary network if available
971      */
onPrimaryWifiInfoChanged( @ullable WifiInfo primaryWifiInfo, @Nullable NetworkInfo networkInfo)972     synchronized void onPrimaryWifiInfoChanged(
973             @Nullable WifiInfo primaryWifiInfo, @Nullable NetworkInfo networkInfo) {
974         if (primaryWifiInfo == null || !connectionInfoMatches(primaryWifiInfo)) {
975             if (mNetworkInfo != null) {
976                 mNetworkInfo = null;
977                 notifyOnUpdated();
978             }
979             return;
980         }
981         if (networkInfo != null) {
982             mNetworkInfo = networkInfo;
983         }
984         updateWifiInfo(primaryWifiInfo);
985         notifyOnUpdated();
986     }
987 
988     /**
989      * Updates this WifiEntry with the given NetworkCapabilities if it matches.
990      */
991     @WorkerThread
onNetworkCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities capabilities)992     synchronized void onNetworkCapabilitiesChanged(
993             @NonNull Network network,
994             @NonNull NetworkCapabilities capabilities) {
995         WifiInfo wifiInfo = Utils.getWifiInfo(capabilities);
996         if (wifiInfo == null) {
997             return;
998         }
999 
1000         if (!connectionInfoMatches(wifiInfo)) {
1001             if (network.equals(mNetwork)) {
1002                 // WifiInfo doesn't match but the Network matches. This may be due to linked
1003                 // roaming, so treat as a disconnect.
1004                 onNetworkLost(network);
1005             }
1006             return;
1007         }
1008 
1009         // Treat non-primary, non-OEM connections as disconnected.
1010         if (!NonSdkApiWrapper.isPrimary(wifiInfo)
1011                 && !NonSdkApiWrapper.isOemCapabilities(capabilities)) {
1012             onNetworkLost(network);
1013             return;
1014         }
1015 
1016         // Connection info matches, so the Network/NetworkCapabilities represent this network
1017         // and the network is currently connecting or connected.
1018         mNetwork = network;
1019         mNetworkCapabilities = capabilities;
1020         updateWifiInfo(wifiInfo);
1021         notifyOnUpdated();
1022     }
1023 
updateWifiInfo(WifiInfo wifiInfo)1024     private synchronized void updateWifiInfo(WifiInfo wifiInfo) {
1025         if (wifiInfo == null) {
1026             mWifiInfo = null;
1027             mConnectedInfo = null;
1028             updateSecurityTypes();
1029             return;
1030         }
1031         mWifiInfo = wifiInfo;
1032         final int wifiInfoRssi = mWifiInfo.getRssi();
1033         if (wifiInfoRssi != INVALID_RSSI) {
1034             mLevel = mWifiManager.calculateSignalLevel(wifiInfoRssi);
1035         }
1036         if (getConnectedState() == CONNECTED_STATE_CONNECTED) {
1037             if (mCalledConnect) {
1038                 mCalledConnect = false;
1039                 mCallbackHandler.post(() -> {
1040                     final ConnectCallback connectCallback = mConnectCallback;
1041                     if (connectCallback != null) {
1042                         connectCallback.onConnectResult(
1043                                 ConnectCallback.CONNECT_STATUS_SUCCESS);
1044                     }
1045                 });
1046             }
1047 
1048             if (mConnectedInfo == null) {
1049                 mConnectedInfo = new ConnectedInfo();
1050             }
1051             mConnectedInfo.frequencyMhz = mWifiInfo.getFrequency();
1052             mConnectedInfo.linkSpeedMbps = mWifiInfo.getLinkSpeed();
1053             mConnectedInfo.wifiStandard = mWifiInfo.getWifiStandard();
1054         }
1055         updateSecurityTypes();
1056     }
1057 
1058     /**
1059      * Updates this WifiEntry as disconnected if the network matches.
1060      * @param network Network that was lost
1061      */
onNetworkLost(@onNull Network network)1062     synchronized void onNetworkLost(@NonNull Network network) {
1063         if (!network.equals(mNetwork)) {
1064             return;
1065         }
1066         // Network matches, so this network is disconnected.
1067         clearConnectionInfo();
1068     }
1069 
1070     /**
1071      * Clears any connection info from this entry.
1072      */
clearConnectionInfo()1073     synchronized void clearConnectionInfo() {
1074         updateWifiInfo(null);
1075         mNetworkInfo = null;
1076         mNetworkCapabilities = null;
1077         mConnectivityReport = null;
1078         if (mCalledDisconnect) {
1079             mCalledDisconnect = false;
1080             mCallbackHandler.post(() -> {
1081                 final DisconnectCallback disconnectCallback = mDisconnectCallback;
1082                 if (disconnectCallback != null) {
1083                     disconnectCallback.onDisconnectResult(
1084                             DisconnectCallback.DISCONNECT_STATUS_SUCCESS);
1085                 }
1086             });
1087         }
1088         notifyOnUpdated();
1089     }
1090 
1091     /**
1092      * Updates this WifiEntry as the default network if it matches.
1093      */
1094     @WorkerThread
onDefaultNetworkCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities capabilities)1095     synchronized void onDefaultNetworkCapabilitiesChanged(
1096             @NonNull Network network,
1097             @NonNull NetworkCapabilities capabilities) {
1098         mDefaultNetwork = network;
1099         mDefaultNetworkCapabilities = capabilities;
1100         notifyOnUpdated();
1101     }
1102 
1103     /**
1104      * Notifies this WifiEntry that the default network was lost.
1105      */
onDefaultNetworkLost()1106     synchronized void onDefaultNetworkLost() {
1107         mDefaultNetwork = null;
1108         mDefaultNetworkCapabilities = null;
1109         notifyOnUpdated();
1110     }
1111 
1112     // Called to indicate the security types should be updated to match new information about the
1113     // network.
updateSecurityTypes()1114     protected void updateSecurityTypes() {
1115         // Do nothing;
1116     }
1117 
1118     // Updates this WifiEntry's link properties if the network matches.
1119     @WorkerThread
updateLinkProperties( @onNull Network network, @NonNull LinkProperties linkProperties)1120     synchronized void updateLinkProperties(
1121             @NonNull Network network, @NonNull LinkProperties linkProperties) {
1122         if (!network.equals(mNetwork)) {
1123             return;
1124         }
1125 
1126         if (mConnectedInfo == null) {
1127             mConnectedInfo = new ConnectedInfo();
1128         }
1129         // Find IPv4 and IPv6 addresses, and subnet mask
1130         List<String> ipv6Addresses = new ArrayList<>();
1131         for (LinkAddress addr : linkProperties.getLinkAddresses()) {
1132             if (addr.getAddress() instanceof Inet4Address) {
1133                 mConnectedInfo.ipAddress = addr.getAddress().getHostAddress();
1134                 try {
1135                     InetAddress all = InetAddress.getByAddress(
1136                             new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
1137                     mConnectedInfo.subnetMask = getNetworkPart(
1138                             all, addr.getPrefixLength()).getHostAddress();
1139                 } catch (UnknownHostException | IllegalArgumentException e) {
1140                     // Leave subnet null;
1141                 }
1142             } else if (addr.getAddress() instanceof Inet6Address) {
1143                 ipv6Addresses.add(addr.getAddress().getHostAddress());
1144             }
1145         }
1146         mConnectedInfo.ipv6Addresses = ipv6Addresses;
1147 
1148         // Find IPv4 default gateway.
1149         for (RouteInfo routeInfo : linkProperties.getRoutes()) {
1150             if (routeInfo.isDefaultRoute() && routeInfo.getDestination().getAddress()
1151                     instanceof Inet4Address && routeInfo.hasGateway()) {
1152                 mConnectedInfo.gateway = routeInfo.getGateway().getHostAddress();
1153                 break;
1154             }
1155         }
1156 
1157         // Find DNS servers
1158         mConnectedInfo.dnsServers = linkProperties.getDnsServers().stream()
1159                 .map(InetAddress::getHostAddress).collect(Collectors.toList());
1160 
1161         notifyOnUpdated();
1162     }
1163 
1164     // Method for WifiTracker to update a connected WifiEntry's validation status.
1165     @WorkerThread
updateConnectivityReport( @onNull ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport)1166     synchronized void updateConnectivityReport(
1167             @NonNull ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport) {
1168         if (connectivityReport.getNetwork().equals(mNetwork)) {
1169             mConnectivityReport = connectivityReport;
1170             notifyOnUpdated();
1171         }
1172     }
1173 
getWifiInfoDescription()1174     synchronized String getWifiInfoDescription() {
1175         final StringJoiner sj = new StringJoiner(" ");
1176         if (getConnectedState() == CONNECTED_STATE_CONNECTED && mWifiInfo != null) {
1177             sj.add("f = " + mWifiInfo.getFrequency());
1178             final String bssid = mWifiInfo.getBSSID();
1179             if (bssid != null) {
1180                 sj.add(bssid);
1181             }
1182             sj.add("standard = " + getStandardString());
1183             sj.add("rssi = " + mWifiInfo.getRssi());
1184             sj.add("score = " + mWifiInfo.getScore());
1185             sj.add(String.format(" tx=%.1f,", mWifiInfo.getSuccessfulTxPacketsPerSecond()));
1186             sj.add(String.format("%.1f,", mWifiInfo.getRetriedTxPacketsPerSecond()));
1187             sj.add(String.format("%.1f ", mWifiInfo.getLostTxPacketsPerSecond()));
1188             sj.add(String.format("rx=%.1f", mWifiInfo.getSuccessfulRxPacketsPerSecond()));
1189             if (BuildCompat.isAtLeastT() && mWifiInfo.getApMldMacAddress() != null) {
1190                 sj.add("mldMac = " + mWifiInfo.getApMldMacAddress());
1191                 sj.add("linkId = " + mWifiInfo.getApMloLinkId());
1192                 sj.add("affLinks = " + Arrays.toString(
1193                         mWifiInfo.getAffiliatedMloLinks().toArray()));
1194             }
1195         }
1196         return sj.toString();
1197     }
1198 
1199     protected class ConnectActionListener implements WifiManager.ActionListener {
1200         @Override
onSuccess()1201         public void onSuccess() {
1202             synchronized (WifiEntry.this) {
1203                 // Wait for L3 connection before returning the success result.
1204                 mCalledConnect = true;
1205             }
1206         }
1207 
1208         @Override
onFailure(int i)1209         public void onFailure(int i) {
1210             mCallbackHandler.post(() -> {
1211                 final ConnectCallback connectCallback = mConnectCallback;
1212                 if (connectCallback != null) {
1213                     connectCallback.onConnectResult(ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN);
1214                 }
1215             });
1216         }
1217     }
1218 
1219     protected class ForgetActionListener implements WifiManager.ActionListener {
1220         @Override
onSuccess()1221         public void onSuccess() {
1222             mCallbackHandler.post(() -> {
1223                 final ForgetCallback forgetCallback = mForgetCallback;
1224                 if (forgetCallback != null) {
1225                     forgetCallback.onForgetResult(ForgetCallback.FORGET_STATUS_SUCCESS);
1226                 }
1227             });
1228         }
1229 
1230         @Override
onFailure(int i)1231         public void onFailure(int i) {
1232             mCallbackHandler.post(() -> {
1233                 final ForgetCallback forgetCallback = mForgetCallback;
1234                 if (forgetCallback != null) {
1235                     forgetCallback.onForgetResult(ForgetCallback.FORGET_STATUS_FAILURE_UNKNOWN);
1236                 }
1237             });
1238         }
1239     }
1240 
1241     @Override
equals(Object other)1242     public boolean equals(Object other) {
1243         if (!(other instanceof WifiEntry)) return false;
1244         return getKey().equals(((WifiEntry) other).getKey());
1245     }
1246 
1247     @Override
hashCode()1248     public int hashCode() {
1249         return getKey().hashCode();
1250     }
1251 
1252     @Override
toString()1253     public String toString() {
1254         StringJoiner sj = new StringJoiner("][", "[", "]");
1255         sj.add(this.getClass().getSimpleName());
1256         sj.add(getTitle());
1257         sj.add(getSummary());
1258         sj.add("Level:" + getLevel() + (shouldShowXLevelIcon() ? "!" : ""));
1259         String security = getSecurityString(true);
1260         if (!TextUtils.isEmpty(security)) {
1261             sj.add(security);
1262         }
1263         int connectedState = getConnectedState();
1264         if (connectedState == CONNECTED_STATE_CONNECTED) {
1265             sj.add("Connected");
1266         } else if (connectedState == CONNECTED_STATE_CONNECTING) {
1267             sj.add("Connecting...");
1268         }
1269         if (hasInternetAccess()) {
1270             sj.add("Internet");
1271         }
1272         if (isDefaultNetwork()) {
1273             sj.add("Default");
1274         }
1275         if (isPrimaryNetwork()) {
1276             sj.add("Primary");
1277         }
1278         if (isLowQuality()) {
1279             sj.add("LowQuality");
1280         }
1281         if (isSaved()) {
1282             sj.add("Saved");
1283         }
1284         if (isSubscription()) {
1285             sj.add("Subscription");
1286         }
1287         if (isSuggestion()) {
1288             sj.add("Suggestion");
1289         }
1290         if (isMetered()) {
1291             sj.add("Metered");
1292         }
1293         if ((isSaved() || isSuggestion() || isSubscription()) && !isAutoJoinEnabled()) {
1294             sj.add("AutoJoinDisabled");
1295         }
1296         if (isExpired()) {
1297             sj.add("Expired");
1298         }
1299         if (canSignIn()) {
1300             sj.add("SignIn");
1301         }
1302         if (shouldEditBeforeConnect()) {
1303             sj.add("EditBeforeConnect");
1304         }
1305         if (hasAdminRestrictions()) {
1306             sj.add("AdminRestricted");
1307         }
1308         return sj.toString();
1309     }
1310 
1311     /**
1312      * The action used to execute the calling of WifiEntry#manageSubscription.
1313      */
1314     public interface ManageSubscriptionAction {
1315         /**
1316          * Execute the action of managing subscription.
1317          */
onExecute()1318         void onExecute();
1319     }
1320 
1321     /**
1322      * Whether this WifiEntry is using a verbose summary.
1323      */
isVerboseSummaryEnabled()1324     public boolean isVerboseSummaryEnabled() {
1325         return mInjector.isVerboseSummaryEnabled();
1326     }
1327 }
1328