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