1 /*
2  * Copyright (C) 2015 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.settingslib.wifi;
18 
19 import android.app.AppGlobals;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.IPackageManager;
23 import android.content.pm.PackageManager;
24 import android.net.ConnectivityManager;
25 import android.net.NetworkBadging;
26 import android.net.NetworkCapabilities;
27 import android.net.NetworkInfo;
28 import android.net.NetworkInfo.DetailedState;
29 import android.net.NetworkInfo.State;
30 import android.net.NetworkScoreManager;
31 import android.net.NetworkScorerAppData;
32 import android.net.ScoredNetwork;
33 import android.net.wifi.IWifiManager;
34 import android.net.wifi.ScanResult;
35 import android.net.wifi.WifiConfiguration;
36 import android.net.wifi.WifiConfiguration.KeyMgmt;
37 import android.net.wifi.WifiInfo;
38 import android.net.wifi.WifiManager;
39 import android.net.wifi.WifiNetworkScoreCache;
40 import android.net.wifi.hotspot2.PasspointConfiguration;
41 import android.os.Bundle;
42 import android.os.RemoteException;
43 import android.os.ServiceManager;
44 import android.os.SystemClock;
45 import android.os.UserHandle;
46 import android.support.annotation.NonNull;
47 import android.text.Spannable;
48 import android.text.SpannableString;
49 import android.text.TextUtils;
50 import android.text.style.TtsSpan;
51 import android.util.Log;
52 
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.settingslib.R;
55 
56 import java.util.ArrayList;
57 import java.util.Iterator;
58 import java.util.concurrent.ConcurrentHashMap;
59 import java.util.concurrent.atomic.AtomicInteger;
60 
61 
62 public class AccessPoint implements Comparable<AccessPoint> {
63     static final String TAG = "SettingsLib.AccessPoint";
64 
65     /**
66      * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
67      */
68     public static final int LOWER_FREQ_24GHZ = 2400;
69 
70     /**
71      * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
72      */
73     public static final int HIGHER_FREQ_24GHZ = 2500;
74 
75     /**
76      * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
77      */
78     public static final int LOWER_FREQ_5GHZ = 4900;
79 
80     /**
81      * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
82      */
83     public static final int HIGHER_FREQ_5GHZ = 5900;
84 
85 
86     /**
87      * Experimental: we should be able to show the user the list of BSSIDs and bands
88      *  for that SSID.
89      *  For now this data is used only with Verbose Logging so as to show the band and number
90      *  of BSSIDs on which that network is seen.
91      */
92     private final ConcurrentHashMap<String, ScanResult> mScanResultCache =
93             new ConcurrentHashMap<String, ScanResult>(32);
94     private static final long MAX_SCAN_RESULT_AGE_MS = 15000;
95 
96     static final String KEY_NETWORKINFO = "key_networkinfo";
97     static final String KEY_WIFIINFO = "key_wifiinfo";
98     static final String KEY_SCANRESULT = "key_scanresult";
99     static final String KEY_SSID = "key_ssid";
100     static final String KEY_SECURITY = "key_security";
101     static final String KEY_PSKTYPE = "key_psktype";
102     static final String KEY_SCANRESULTCACHE = "key_scanresultcache";
103     static final String KEY_CONFIG = "key_config";
104     static final String KEY_FQDN = "key_fqdn";
105     static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
106     static final AtomicInteger sLastId = new AtomicInteger(0);
107 
108     /**
109      * These values are matched in string arrays -- changes must be kept in sync
110      */
111     public static final int SECURITY_NONE = 0;
112     public static final int SECURITY_WEP = 1;
113     public static final int SECURITY_PSK = 2;
114     public static final int SECURITY_EAP = 3;
115 
116     private static final int PSK_UNKNOWN = 0;
117     private static final int PSK_WPA = 1;
118     private static final int PSK_WPA2 = 2;
119     private static final int PSK_WPA_WPA2 = 3;
120 
121     /**
122      * The number of distinct wifi levels.
123      *
124      * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
125      */
126     public static final int SIGNAL_LEVELS = 5;
127 
128     public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
129 
130     private final Context mContext;
131 
132     private String ssid;
133     private String bssid;
134     private int security;
135     private int networkId = WifiConfiguration.INVALID_NETWORK_ID;
136 
137     private int pskType = PSK_UNKNOWN;
138 
139     private WifiConfiguration mConfig;
140 
141     private int mRssi = UNREACHABLE_RSSI;
142     private long mSeen = 0;
143 
144     private WifiInfo mInfo;
145     private NetworkInfo mNetworkInfo;
146     AccessPointListener mAccessPointListener;
147 
148     private Object mTag;
149 
150     private int mRankingScore = Integer.MIN_VALUE;
151     private int mBadge = NetworkBadging.BADGING_NONE;
152     private boolean mIsScoredNetworkMetered = false;
153 
154     // used to co-relate internal vs returned accesspoint.
155     int mId;
156 
157     /**
158      * Information associated with the {@link PasspointConfiguration}.  Only maintaining
159      * the relevant info to preserve spaces.
160      */
161     private String mFqdn;
162     private String mProviderFriendlyName;
163 
AccessPoint(Context context, Bundle savedState)164     public AccessPoint(Context context, Bundle savedState) {
165         mContext = context;
166         mConfig = savedState.getParcelable(KEY_CONFIG);
167         if (mConfig != null) {
168             loadConfig(mConfig);
169         }
170         if (savedState.containsKey(KEY_SSID)) {
171             ssid = savedState.getString(KEY_SSID);
172         }
173         if (savedState.containsKey(KEY_SECURITY)) {
174             security = savedState.getInt(KEY_SECURITY);
175         }
176         if (savedState.containsKey(KEY_PSKTYPE)) {
177             pskType = savedState.getInt(KEY_PSKTYPE);
178         }
179         mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
180         if (savedState.containsKey(KEY_NETWORKINFO)) {
181             mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
182         }
183         if (savedState.containsKey(KEY_SCANRESULTCACHE)) {
184             ArrayList<ScanResult> scanResultArrayList =
185                     savedState.getParcelableArrayList(KEY_SCANRESULTCACHE);
186             mScanResultCache.clear();
187             for (ScanResult result : scanResultArrayList) {
188                 mScanResultCache.put(result.BSSID, result);
189             }
190         }
191         if (savedState.containsKey(KEY_FQDN)) {
192             mFqdn = savedState.getString(KEY_FQDN);
193         }
194         if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
195             mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
196         }
197         update(mConfig, mInfo, mNetworkInfo);
198         updateRssi();
199         updateSeen();
200         mId = sLastId.incrementAndGet();
201     }
202 
AccessPoint(Context context, WifiConfiguration config)203     public AccessPoint(Context context, WifiConfiguration config) {
204         mContext = context;
205         loadConfig(config);
206         mId = sLastId.incrementAndGet();
207     }
208 
209     /**
210      * Initialize an AccessPoint object for a {@link PasspointConfiguration}.  This is mainly
211      * used by "Saved Networks" page for managing the saved {@link PasspointConfiguration}.
212      */
AccessPoint(Context context, PasspointConfiguration config)213     public AccessPoint(Context context, PasspointConfiguration config) {
214         mContext = context;
215         mFqdn = config.getHomeSp().getFqdn();
216         mProviderFriendlyName = config.getHomeSp().getFriendlyName();
217         mId = sLastId.incrementAndGet();
218     }
219 
AccessPoint(Context context, AccessPoint other)220     AccessPoint(Context context, AccessPoint other) {
221         mContext = context;
222         copyFrom(other);
223     }
224 
AccessPoint(Context context, ScanResult result)225     AccessPoint(Context context, ScanResult result) {
226         mContext = context;
227         initWithScanResult(result);
228         mId = sLastId.incrementAndGet();
229     }
230 
231     /**
232      * Copy accesspoint information. NOTE: We do not copy tag information because that is never
233      * set on the internal copy.
234      * @param that
235      */
copyFrom(AccessPoint that)236     void copyFrom(AccessPoint that) {
237         that.evictOldScanResults();
238         this.ssid = that.ssid;
239         this.bssid = that.bssid;
240         this.security = that.security;
241         this.networkId = that.networkId;
242         this.pskType = that.pskType;
243         this.mConfig = that.mConfig; //TODO: Watch out, this object is mutated.
244         this.mRssi = that.mRssi;
245         this.mSeen = that.mSeen;
246         this.mInfo = that.mInfo;
247         this.mNetworkInfo = that.mNetworkInfo;
248         this.mScanResultCache.clear();
249         this.mScanResultCache.putAll(that.mScanResultCache);
250         this.mId = that.mId;
251         this.mBadge = that.mBadge;
252         this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
253         this.mRankingScore = that.mRankingScore;
254     }
255 
256     /**
257     * Returns a negative integer, zero, or a positive integer if this AccessPoint is less than,
258     * equal to, or greater than the other AccessPoint.
259     *
260     * Sort order rules for AccessPoints:
261     *   1. Active before inactive
262     *   2. Reachable before unreachable
263     *   3. Saved before unsaved
264     *   4. (Internal only) Network ranking score
265     *   5. Stronger signal before weaker signal
266     *   6. SSID alphabetically
267     *
268     * Note that AccessPoints with a signal are usually also Reachable,
269     * and will thus appear before unreachable saved AccessPoints.
270     */
271     @Override
compareTo(@onNull AccessPoint other)272     public int compareTo(@NonNull AccessPoint other) {
273         // Active one goes first.
274         if (isActive() && !other.isActive()) return -1;
275         if (!isActive() && other.isActive()) return 1;
276 
277         // Reachable one goes before unreachable one.
278         if (isReachable() && !other.isReachable()) return -1;
279         if (!isReachable() && other.isReachable()) return 1;
280 
281         // Configured (saved) one goes before unconfigured one.
282         if (isSaved() && !other.isSaved()) return -1;
283         if (!isSaved() && other.isSaved()) return 1;
284 
285         // Higher scores go before lower scores
286         if (getRankingScore() != other.getRankingScore()) {
287             return (getRankingScore() > other.getRankingScore()) ? -1 : 1;
288         }
289 
290         // Sort by signal strength, bucketed by level
291         int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS)
292                 - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
293         if (difference != 0) {
294             return difference;
295         }
296         // Sort by ssid.
297         return getSsidStr().compareToIgnoreCase(other.getSsidStr());
298     }
299 
300     @Override
equals(Object other)301     public boolean equals(Object other) {
302         if (!(other instanceof AccessPoint)) return false;
303         return (this.compareTo((AccessPoint) other) == 0);
304     }
305 
306     @Override
hashCode()307     public int hashCode() {
308         int result = 0;
309         if (mInfo != null) result += 13 * mInfo.hashCode();
310         result += 19 * mRssi;
311         result += 23 * networkId;
312         result += 29 * ssid.hashCode();
313         return result;
314     }
315 
316     @Override
toString()317     public String toString() {
318         StringBuilder builder = new StringBuilder().append("AccessPoint(")
319                 .append(ssid);
320         if (bssid != null) {
321             builder.append(":").append(bssid);
322         }
323         if (isSaved()) {
324             builder.append(',').append("saved");
325         }
326         if (isActive()) {
327             builder.append(',').append("active");
328         }
329         if (isEphemeral()) {
330             builder.append(',').append("ephemeral");
331         }
332         if (isConnectable()) {
333             builder.append(',').append("connectable");
334         }
335         if (security != SECURITY_NONE) {
336             builder.append(',').append(securityToString(security, pskType));
337         }
338         builder.append(",mRssi=").append(mRssi);
339         builder.append(",level=").append(getLevel());
340         if (mRankingScore != Integer.MIN_VALUE) {
341             builder.append(",rankingScore=").append(mRankingScore);
342         }
343         if (mBadge != NetworkBadging.BADGING_NONE) {
344             builder.append(",badge=").append(mBadge);
345         }
346         builder.append(",metered=").append(isMetered());
347 
348         return builder.append(')').toString();
349     }
350 
351     /**
352      * Updates the AccessPoint rankingScore, metering, and badge, returning true if the data has
353      * changed.
354      *
355      * @param scoreCache The score cache to use to retrieve scores.
356      * @param scoringUiEnabled Whether to show scoring and badging UI.
357      */
update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled)358     boolean update(WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled) {
359         boolean scoreChanged = false;
360         if (scoringUiEnabled) {
361             scoreChanged = updateScores(scoreCache);
362         }
363         return updateMetered(scoreCache) || scoreChanged;
364     }
365 
366     /**
367      * Updates the AccessPoint rankingScore and badge, returning true if the data has changed.
368      *
369      * @param scoreCache The score cache to use to retrieve scores.
370      */
updateScores(WifiNetworkScoreCache scoreCache)371     private boolean updateScores(WifiNetworkScoreCache scoreCache) {
372         int oldBadge = mBadge;
373         int oldRankingScore = mRankingScore;
374         mBadge = NetworkBadging.BADGING_NONE;
375         mRankingScore = Integer.MIN_VALUE;
376 
377         for (ScanResult result : mScanResultCache.values()) {
378             ScoredNetwork score = scoreCache.getScoredNetwork(result);
379             if (score == null) {
380                 continue;
381             }
382 
383             if (score.hasRankingScore()) {
384                 mRankingScore = Math.max(mRankingScore, score.calculateRankingScore(result.level));
385             }
386             mBadge = Math.max(mBadge, score.calculateBadge(result.level));
387         }
388 
389         return (oldBadge != mBadge || oldRankingScore != mRankingScore);
390     }
391 
392     /**
393      * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning
394      * true if the metering changed.
395      */
updateMetered(WifiNetworkScoreCache scoreCache)396     private boolean updateMetered(WifiNetworkScoreCache scoreCache) {
397         boolean oldMetering = mIsScoredNetworkMetered;
398         mIsScoredNetworkMetered = false;
399         for (ScanResult result : mScanResultCache.values()) {
400             ScoredNetwork score = scoreCache.getScoredNetwork(result);
401             if (score == null) {
402                 continue;
403             }
404             mIsScoredNetworkMetered |= score.meteredHint;
405         }
406         return oldMetering == mIsScoredNetworkMetered;
407     }
408 
evictOldScanResults()409     private void evictOldScanResults() {
410         long nowMs = SystemClock.elapsedRealtime();
411         for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
412             ScanResult result = iter.next();
413             // result timestamp is in microseconds
414             if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MS) {
415                 iter.remove();
416             }
417         }
418     }
419 
matches(ScanResult result)420     public boolean matches(ScanResult result) {
421         return ssid.equals(result.SSID) && security == getSecurity(result);
422     }
423 
matches(WifiConfiguration config)424     public boolean matches(WifiConfiguration config) {
425         if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) {
426             return ssid.equals(removeDoubleQuotes(config.SSID)) && config.FQDN.equals(mConfig.FQDN);
427         } else {
428             return ssid.equals(removeDoubleQuotes(config.SSID))
429                     && security == getSecurity(config)
430                     && (mConfig == null || mConfig.shared == config.shared);
431         }
432     }
433 
getConfig()434     public WifiConfiguration getConfig() {
435         return mConfig;
436     }
437 
getPasspointFqdn()438     public String getPasspointFqdn() {
439         return mFqdn;
440     }
441 
clearConfig()442     public void clearConfig() {
443         mConfig = null;
444         networkId = WifiConfiguration.INVALID_NETWORK_ID;
445     }
446 
getInfo()447     public WifiInfo getInfo() {
448         return mInfo;
449     }
450 
451     /**
452      * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
453      *
454      * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
455      * always return at least 0.
456      */
getLevel()457     public int getLevel() {
458         return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
459     }
460 
getRssi()461     public int getRssi() {
462         return mRssi;
463     }
464 
465     /**
466      * Updates {@link #mRssi}.
467      *
468      * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned.
469      * If the given AccessPoint is not active, a value will be calculated from previous scan
470      * results, returning the best RSSI for all matching AccessPoints averaged with the previous
471      * value. If the access point is not connected and there are no scan results, the rssi will be
472      * set to {@link #UNREACHABLE_RSSI}.
473      *
474      * <p>Old scan results will be evicted from the cache when this method is invoked.
475      */
updateRssi()476     private void updateRssi() {
477         evictOldScanResults();
478 
479         if (this.isActive()) {
480             return;
481         }
482 
483         int rssi = UNREACHABLE_RSSI;
484         for (ScanResult result : mScanResultCache.values()) {
485             if (result.level > rssi) {
486                 rssi = result.level;
487             }
488         }
489 
490         if (rssi != UNREACHABLE_RSSI && mRssi != UNREACHABLE_RSSI) {
491             mRssi = (mRssi + rssi) / 2; // half-life previous value
492         } else {
493             mRssi = rssi;
494         }
495     }
496 
497     /**
498      * Updates {@link #mSeen} based on the scan result cache.
499      *
500      * <p>Old scan results will be evicted from the cache when this method is invoked.
501      */
updateSeen()502     private void updateSeen() {
503         evictOldScanResults();
504 
505         // TODO(sghuman): Set to now if connected
506 
507         long seen = 0;
508         for (ScanResult result : mScanResultCache.values()) {
509             if (result.timestamp > seen) {
510                 seen = result.timestamp;
511             }
512         }
513 
514         // Only replace the previous value if we have a recent scan result to use
515         if (seen != 0) {
516             mSeen = seen;
517         }
518     }
519 
520     /**
521      * Returns if the network is marked metered. Metering can be marked through its config in
522      * {@link WifiConfiguration}, after connection in {@link WifiInfo}, or from a score config in
523      * {@link ScoredNetwork}.
524      */
isMetered()525     public boolean isMetered() {
526         return mIsScoredNetworkMetered
527                 || (mConfig != null && mConfig.meteredHint)
528                 || (mInfo != null && mInfo.getMeteredHint()
529                 || (mNetworkInfo != null && mNetworkInfo.isMetered()));
530     }
531 
getNetworkInfo()532     public NetworkInfo getNetworkInfo() {
533         return mNetworkInfo;
534     }
535 
getSecurity()536     public int getSecurity() {
537         return security;
538     }
539 
getSecurityString(boolean concise)540     public String getSecurityString(boolean concise) {
541         Context context = mContext;
542         if (mConfig != null && mConfig.isPasspoint()) {
543             return concise ? context.getString(R.string.wifi_security_short_eap) :
544                 context.getString(R.string.wifi_security_eap);
545         }
546         switch(security) {
547             case SECURITY_EAP:
548                 return concise ? context.getString(R.string.wifi_security_short_eap) :
549                     context.getString(R.string.wifi_security_eap);
550             case SECURITY_PSK:
551                 switch (pskType) {
552                     case PSK_WPA:
553                         return concise ? context.getString(R.string.wifi_security_short_wpa) :
554                             context.getString(R.string.wifi_security_wpa);
555                     case PSK_WPA2:
556                         return concise ? context.getString(R.string.wifi_security_short_wpa2) :
557                             context.getString(R.string.wifi_security_wpa2);
558                     case PSK_WPA_WPA2:
559                         return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
560                             context.getString(R.string.wifi_security_wpa_wpa2);
561                     case PSK_UNKNOWN:
562                     default:
563                         return concise ? context.getString(R.string.wifi_security_short_psk_generic)
564                                 : context.getString(R.string.wifi_security_psk_generic);
565                 }
566             case SECURITY_WEP:
567                 return concise ? context.getString(R.string.wifi_security_short_wep) :
568                     context.getString(R.string.wifi_security_wep);
569             case SECURITY_NONE:
570             default:
571                 return concise ? "" : context.getString(R.string.wifi_security_none);
572         }
573     }
574 
getSsidStr()575     public String getSsidStr() {
576         return ssid;
577     }
578 
getBssid()579     public String getBssid() {
580         return bssid;
581     }
582 
getSsid()583     public CharSequence getSsid() {
584         final SpannableString str = new SpannableString(ssid);
585         str.setSpan(new TtsSpan.TelephoneBuilder(ssid).build(), 0, ssid.length(),
586                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);
587         return str;
588     }
589 
getConfigName()590     public String getConfigName() {
591         if (mConfig != null && mConfig.isPasspoint()) {
592             return mConfig.providerFriendlyName;
593         } else if (mFqdn != null) {
594             return mProviderFriendlyName;
595         } else {
596             return ssid;
597         }
598     }
599 
getDetailedState()600     public DetailedState getDetailedState() {
601         if (mNetworkInfo != null) {
602             return mNetworkInfo.getDetailedState();
603         }
604         Log.w(TAG, "NetworkInfo is null, cannot return detailed state");
605         return null;
606     }
607 
getSavedNetworkSummary()608     public String getSavedNetworkSummary() {
609         WifiConfiguration config = mConfig;
610         if (config != null) {
611             PackageManager pm = mContext.getPackageManager();
612             String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID);
613             int userId = UserHandle.getUserId(config.creatorUid);
614             ApplicationInfo appInfo = null;
615             if (config.creatorName != null && config.creatorName.equals(systemName)) {
616                 appInfo = mContext.getApplicationInfo();
617             } else {
618                 try {
619                     IPackageManager ipm = AppGlobals.getPackageManager();
620                     appInfo = ipm.getApplicationInfo(config.creatorName, 0 /* flags */, userId);
621                 } catch (RemoteException rex) {
622                 }
623             }
624             if (appInfo != null &&
625                     !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) &&
626                     !appInfo.packageName.equals(
627                     mContext.getString(R.string.certinstaller_package))) {
628                 return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm));
629             }
630         }
631         return "";
632     }
633 
getSummary()634     public String getSummary() {
635         return getSettingsSummary(mConfig);
636     }
637 
getSettingsSummary()638     public String getSettingsSummary() {
639         return getSettingsSummary(mConfig);
640     }
641 
getSettingsSummary(WifiConfiguration config)642     private String getSettingsSummary(WifiConfiguration config) {
643         // Update to new summary
644         StringBuilder summary = new StringBuilder();
645 
646         if (isActive() && config != null && config.isPasspoint()) {
647             // This is the active connection on passpoint
648             summary.append(getSummary(mContext, getDetailedState(),
649                     false, config.providerFriendlyName));
650         } else if (isActive()) {
651             // This is the active connection on non-passpoint network
652             summary.append(getSummary(mContext, getDetailedState(),
653                     mInfo != null && mInfo.isEphemeral()));
654         } else if (config != null && config.isPasspoint()
655                 && config.getNetworkSelectionStatus().isNetworkEnabled()) {
656             String format = mContext.getString(R.string.available_via_passpoint);
657             summary.append(String.format(format, config.providerFriendlyName));
658         } else if (config != null && config.hasNoInternetAccess()) {
659             int messageID = config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
660                     ? R.string.wifi_no_internet_no_reconnect
661                     : R.string.wifi_no_internet;
662             summary.append(mContext.getString(messageID));
663         } else if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
664             WifiConfiguration.NetworkSelectionStatus networkStatus =
665                     config.getNetworkSelectionStatus();
666             switch (networkStatus.getNetworkSelectionDisableReason()) {
667                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
668                     summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
669                     break;
670                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
671                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
672                     summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
673                     break;
674                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
675                     summary.append(mContext.getString(R.string.wifi_disabled_generic));
676                     break;
677             }
678         } else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
679             summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
680         } else if (!isReachable()) { // Wifi out of range
681             summary.append(mContext.getString(R.string.wifi_not_in_range));
682         } else { // In range, not disabled.
683             if (config != null) { // Is saved network
684                 summary.append(mContext.getString(R.string.wifi_remembered));
685             }
686         }
687 
688         if (WifiTracker.sVerboseLogging > 0) {
689             // Add RSSI/band information for this config, what was seen up to 6 seconds ago
690             // verbose WiFi Logging is only turned on thru developers settings
691             if (mInfo != null && mNetworkInfo != null) { // This is the active connection
692                 summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
693             }
694             summary.append(" " + getVisibilityStatus());
695             if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
696                 summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
697                 if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
698                     long now = System.currentTimeMillis();
699                     long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
700                     long sec = diff%60; //seconds
701                     long min = (diff/60)%60; //minutes
702                     long hour = (min/60)%60; //hours
703                     summary.append(", ");
704                     if (hour > 0) summary.append(Long.toString(hour) + "h ");
705                     summary.append( Long.toString(min) + "m ");
706                     summary.append( Long.toString(sec) + "s ");
707                 }
708                 summary.append(")");
709             }
710 
711             if (config != null) {
712                 WifiConfiguration.NetworkSelectionStatus networkStatus =
713                         config.getNetworkSelectionStatus();
714                 for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
715                         index < WifiConfiguration.NetworkSelectionStatus
716                         .NETWORK_SELECTION_DISABLED_MAX; index++) {
717                     if (networkStatus.getDisableReasonCounter(index) != 0) {
718                         summary.append(" " + WifiConfiguration.NetworkSelectionStatus
719                                 .getNetworkDisableReasonString(index) + "="
720                                 + networkStatus.getDisableReasonCounter(index));
721                     }
722                 }
723             }
724         }
725         return summary.toString();
726     }
727 
728     /**
729      * Returns the visibility status of the WifiConfiguration.
730      *
731      * @return autojoin debugging information
732      * TODO: use a string formatter
733      * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
734      * For instance [-40,5/-30,2]
735      */
getVisibilityStatus()736     private String getVisibilityStatus() {
737         StringBuilder visibility = new StringBuilder();
738         StringBuilder scans24GHz = null;
739         StringBuilder scans5GHz = null;
740         String bssid = null;
741 
742         long now = System.currentTimeMillis();
743 
744         if (mInfo != null) {
745             bssid = mInfo.getBSSID();
746             if (bssid != null) {
747                 visibility.append(" ").append(bssid);
748             }
749             visibility.append(" rssi=").append(mInfo.getRssi());
750             visibility.append(" ");
751             visibility.append(" score=").append(mInfo.score);
752             if (mRankingScore != Integer.MIN_VALUE) {
753               visibility.append(" rankingScore=").append(getRankingScore());
754             }
755             if (mBadge != NetworkBadging.BADGING_NONE) {
756               visibility.append(" badge=").append(getBadge());
757             }
758             visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
759             visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
760             visibility.append(String.format("%.1f ", mInfo.txBadRate));
761             visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
762         }
763 
764         int rssi5 = WifiConfiguration.INVALID_RSSI;
765         int rssi24 = WifiConfiguration.INVALID_RSSI;
766         int num5 = 0;
767         int num24 = 0;
768         int numBlackListed = 0;
769         int n24 = 0; // Number scan results we included in the string
770         int n5 = 0; // Number scan results we included in the string
771         evictOldScanResults();
772         // TODO: sort list by RSSI or age
773         for (ScanResult result : mScanResultCache.values()) {
774 
775             if (result.frequency >= LOWER_FREQ_5GHZ
776                     && result.frequency <= HIGHER_FREQ_5GHZ) {
777                 // Strictly speaking: [4915, 5825]
778                 // number of known BSSID on 5GHz band
779                 num5 = num5 + 1;
780             } else if (result.frequency >= LOWER_FREQ_24GHZ
781                     && result.frequency <= HIGHER_FREQ_24GHZ) {
782                 // Strictly speaking: [2412, 2482]
783                 // number of known BSSID on 2.4Ghz band
784                 num24 = num24 + 1;
785             }
786 
787 
788             if (result.frequency >= LOWER_FREQ_5GHZ
789                     && result.frequency <= HIGHER_FREQ_5GHZ) {
790                 if (result.level > rssi5) {
791                     rssi5 = result.level;
792                 }
793                 if (n5 < 4) {
794                     if (scans5GHz == null) scans5GHz = new StringBuilder();
795                     scans5GHz.append(" \n{").append(result.BSSID);
796                     if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*");
797                     scans5GHz.append("=").append(result.frequency);
798                     scans5GHz.append(",").append(result.level);
799                     scans5GHz.append("}");
800                     n5++;
801                 }
802             } else if (result.frequency >= LOWER_FREQ_24GHZ
803                     && result.frequency <= HIGHER_FREQ_24GHZ) {
804                 if (result.level > rssi24) {
805                     rssi24 = result.level;
806                 }
807                 if (n24 < 4) {
808                     if (scans24GHz == null) scans24GHz = new StringBuilder();
809                     scans24GHz.append(" \n{").append(result.BSSID);
810                     if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*");
811                     scans24GHz.append("=").append(result.frequency);
812                     scans24GHz.append(",").append(result.level);
813                     scans24GHz.append("}");
814                     n24++;
815                 }
816             }
817         }
818         visibility.append(" [");
819         if (num24 > 0) {
820             visibility.append("(").append(num24).append(")");
821             if (n24 <= 4) {
822                 if (scans24GHz != null) {
823                     visibility.append(scans24GHz.toString());
824                 }
825             } else {
826                 visibility.append("max=").append(rssi24);
827                 if (scans24GHz != null) {
828                     visibility.append(",").append(scans24GHz.toString());
829                 }
830             }
831         }
832         visibility.append(";");
833         if (num5 > 0) {
834             visibility.append("(").append(num5).append(")");
835             if (n5 <= 4) {
836                 if (scans5GHz != null) {
837                     visibility.append(scans5GHz.toString());
838                 }
839             } else {
840                 visibility.append("max=").append(rssi5);
841                 if (scans5GHz != null) {
842                     visibility.append(",").append(scans5GHz.toString());
843                 }
844             }
845         }
846         if (numBlackListed > 0)
847             visibility.append("!").append(numBlackListed);
848         visibility.append("]");
849 
850         return visibility.toString();
851     }
852 
853     /**
854      * Return whether this is the active connection.
855      * For ephemeral connections (networkId is invalid), this returns false if the network is
856      * disconnected.
857      */
isActive()858     public boolean isActive() {
859         return mNetworkInfo != null &&
860                 (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
861                  mNetworkInfo.getState() != State.DISCONNECTED);
862     }
863 
isConnectable()864     public boolean isConnectable() {
865         return getLevel() != -1 && getDetailedState() == null;
866     }
867 
isEphemeral()868     public boolean isEphemeral() {
869         return mInfo != null && mInfo.isEphemeral() &&
870                 mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED;
871     }
872 
873     /**
874      * Return true if this AccessPoint represents a Passpoint AP.
875      */
isPasspoint()876     public boolean isPasspoint() {
877         return mConfig != null && mConfig.isPasspoint();
878     }
879 
880     /**
881      * Return true if this AccessPoint represents a Passpoint provider configuration.
882      */
isPasspointConfig()883     public boolean isPasspointConfig() {
884         return mFqdn != null;
885     }
886 
887     /**
888      * Return whether the given {@link WifiInfo} is for this access point.
889      * If the current AP does not have a network Id then the config is used to
890      * match based on SSID and security.
891      */
isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info)892     private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
893         if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
894             return networkId == info.getNetworkId();
895         } else if (config != null) {
896             return matches(config);
897         }
898         else {
899             // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
900             // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
901             // TODO: Handle hex string SSIDs.
902             return ssid.equals(removeDoubleQuotes(info.getSSID()));
903         }
904     }
905 
isSaved()906     public boolean isSaved() {
907         return networkId != WifiConfiguration.INVALID_NETWORK_ID;
908     }
909 
getTag()910     public Object getTag() {
911         return mTag;
912     }
913 
setTag(Object tag)914     public void setTag(Object tag) {
915         mTag = tag;
916     }
917 
918     /**
919      * Generate and save a default wifiConfiguration with common values.
920      * Can only be called for unsecured networks.
921      */
generateOpenNetworkConfig()922     public void generateOpenNetworkConfig() {
923         if (security != SECURITY_NONE)
924             throw new IllegalStateException();
925         if (mConfig != null)
926             return;
927         mConfig = new WifiConfiguration();
928         mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
929         mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
930     }
931 
loadConfig(WifiConfiguration config)932     void loadConfig(WifiConfiguration config) {
933         ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
934         bssid = config.BSSID;
935         security = getSecurity(config);
936         networkId = config.networkId;
937         mConfig = config;
938     }
939 
initWithScanResult(ScanResult result)940     private void initWithScanResult(ScanResult result) {
941         ssid = result.SSID;
942         bssid = result.BSSID;
943         security = getSecurity(result);
944         if (security == SECURITY_PSK)
945             pskType = getPskType(result);
946 
947         mScanResultCache.put(result.BSSID, result);
948         updateRssi();
949         mSeen = result.timestamp; // even if the timestamp is old it is still valid
950     }
951 
saveWifiState(Bundle savedState)952     public void saveWifiState(Bundle savedState) {
953         if (ssid != null) savedState.putString(KEY_SSID, getSsidStr());
954         savedState.putInt(KEY_SECURITY, security);
955         savedState.putInt(KEY_PSKTYPE, pskType);
956         if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
957         savedState.putParcelable(KEY_WIFIINFO, mInfo);
958         evictOldScanResults();
959         savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
960                 new ArrayList<ScanResult>(mScanResultCache.values()));
961         if (mNetworkInfo != null) {
962             savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
963         }
964         if (mFqdn != null) {
965             savedState.putString(KEY_FQDN, mFqdn);
966         }
967         if (mProviderFriendlyName != null) {
968             savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
969         }
970     }
971 
setListener(AccessPointListener listener)972     public void setListener(AccessPointListener listener) {
973         mAccessPointListener = listener;
974     }
975 
update(ScanResult result)976     boolean update(ScanResult result) {
977         if (matches(result)) {
978             int oldLevel = getLevel();
979 
980             /* Add or update the scan result for the BSSID */
981             mScanResultCache.put(result.BSSID, result);
982             updateSeen();
983             updateRssi();
984             int newLevel = getLevel();
985 
986             if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) {
987                 mAccessPointListener.onLevelChanged(this);
988             }
989             // This flag only comes from scans, is not easily saved in config
990             if (security == SECURITY_PSK) {
991                 pskType = getPskType(result);
992             }
993 
994             if (mAccessPointListener != null) {
995                 mAccessPointListener.onAccessPointChanged(this);
996             }
997 
998             return true;
999         }
1000         return false;
1001     }
1002 
1003     /** Attempt to update the AccessPoint and return true if an update occurred. */
update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo)1004     public boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
1005         boolean updated = false;
1006         final int oldLevel = getLevel();
1007         if (info != null && isInfoForThisAccessPoint(config, info)) {
1008             updated = (mInfo == null);
1009             if (mRssi != info.getRssi()) {
1010                 mRssi = info.getRssi();
1011                 updated = true;
1012             } else if (mNetworkInfo != null && networkInfo != null
1013                     && mNetworkInfo.getDetailedState() != networkInfo.getDetailedState()) {
1014                 updated = true;
1015             }
1016             mInfo = info;
1017             mNetworkInfo = networkInfo;
1018         } else if (mInfo != null) {
1019             updated = true;
1020             mInfo = null;
1021             mNetworkInfo = null;
1022         }
1023         if (updated && mAccessPointListener != null) {
1024             mAccessPointListener.onAccessPointChanged(this);
1025 
1026             if (oldLevel != getLevel() /* current level */) {
1027                 mAccessPointListener.onLevelChanged(this);
1028             }
1029         }
1030         return updated;
1031     }
1032 
update(WifiConfiguration config)1033     void update(WifiConfiguration config) {
1034         mConfig = config;
1035         networkId = config.networkId;
1036         if (mAccessPointListener != null) {
1037             mAccessPointListener.onAccessPointChanged(this);
1038         }
1039     }
1040 
1041     @VisibleForTesting
setRssi(int rssi)1042     void setRssi(int rssi) {
1043         mRssi = rssi;
1044     }
1045 
1046     /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */
setUnreachable()1047     void setUnreachable() {
1048         setRssi(AccessPoint.UNREACHABLE_RSSI);
1049     }
1050 
getRankingScore()1051     int getRankingScore() {
1052         return mRankingScore;
1053     }
1054 
getBadge()1055     int getBadge() {
1056         return mBadge;
1057     }
1058 
1059     /** Return true if the current RSSI is reachable, and false otherwise. */
isReachable()1060     public boolean isReachable() {
1061         return mRssi != UNREACHABLE_RSSI;
1062     }
1063 
getSummary(Context context, String ssid, DetailedState state, boolean isEphemeral, String passpointProvider)1064     public static String getSummary(Context context, String ssid, DetailedState state,
1065             boolean isEphemeral, String passpointProvider) {
1066         if (state == DetailedState.CONNECTED && ssid == null) {
1067             if (TextUtils.isEmpty(passpointProvider) == false) {
1068                 // Special case for connected + passpoint networks.
1069                 String format = context.getString(R.string.connected_via_passpoint);
1070                 return String.format(format, passpointProvider);
1071             } else if (isEphemeral) {
1072                 // Special case for connected + ephemeral networks.
1073                 final NetworkScoreManager networkScoreManager = context.getSystemService(
1074                         NetworkScoreManager.class);
1075                 NetworkScorerAppData scorer = networkScoreManager.getActiveScorer();
1076                 if (scorer != null && scorer.getRecommendationServiceLabel() != null) {
1077                     String format = context.getString(R.string.connected_via_network_scorer);
1078                     return String.format(format, scorer.getRecommendationServiceLabel());
1079                 } else {
1080                     return context.getString(R.string.connected_via_network_scorer_default);
1081                 }
1082             }
1083         }
1084 
1085         // Case when there is wifi connected without internet connectivity.
1086         final ConnectivityManager cm = (ConnectivityManager)
1087                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
1088         if (state == DetailedState.CONNECTED) {
1089             IWifiManager wifiManager = IWifiManager.Stub.asInterface(
1090                     ServiceManager.getService(Context.WIFI_SERVICE));
1091             NetworkCapabilities nc = null;
1092 
1093             try {
1094                 nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
1095             } catch (RemoteException e) {}
1096 
1097             if (nc != null) {
1098                 if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) {
1099                     return context.getString(
1100                         com.android.internal.R.string.network_available_sign_in);
1101                 } else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
1102                     return context.getString(R.string.wifi_connected_no_internet);
1103                 }
1104             }
1105         }
1106         if (state == null) {
1107             Log.w(TAG, "state is null, returning empty summary");
1108             return "";
1109         }
1110         String[] formats = context.getResources().getStringArray((ssid == null)
1111                 ? R.array.wifi_status : R.array.wifi_status_with_ssid);
1112         int index = state.ordinal();
1113 
1114         if (index >= formats.length || formats[index].length() == 0) {
1115             return "";
1116         }
1117         return String.format(formats[index], ssid);
1118     }
1119 
getSummary(Context context, DetailedState state, boolean isEphemeral)1120     public static String getSummary(Context context, DetailedState state, boolean isEphemeral) {
1121         return getSummary(context, null, state, isEphemeral, null);
1122     }
1123 
getSummary(Context context, DetailedState state, boolean isEphemeral, String passpointProvider)1124     public static String getSummary(Context context, DetailedState state, boolean isEphemeral,
1125             String passpointProvider) {
1126         return getSummary(context, null, state, isEphemeral, passpointProvider);
1127     }
1128 
convertToQuotedString(String string)1129     public static String convertToQuotedString(String string) {
1130         return "\"" + string + "\"";
1131     }
1132 
getPskType(ScanResult result)1133     private static int getPskType(ScanResult result) {
1134         boolean wpa = result.capabilities.contains("WPA-PSK");
1135         boolean wpa2 = result.capabilities.contains("WPA2-PSK");
1136         if (wpa2 && wpa) {
1137             return PSK_WPA_WPA2;
1138         } else if (wpa2) {
1139             return PSK_WPA2;
1140         } else if (wpa) {
1141             return PSK_WPA;
1142         } else {
1143             Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
1144             return PSK_UNKNOWN;
1145         }
1146     }
1147 
getSecurity(ScanResult result)1148     private static int getSecurity(ScanResult result) {
1149         if (result.capabilities.contains("WEP")) {
1150             return SECURITY_WEP;
1151         } else if (result.capabilities.contains("PSK")) {
1152             return SECURITY_PSK;
1153         } else if (result.capabilities.contains("EAP")) {
1154             return SECURITY_EAP;
1155         }
1156         return SECURITY_NONE;
1157     }
1158 
getSecurity(WifiConfiguration config)1159     static int getSecurity(WifiConfiguration config) {
1160         if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
1161             return SECURITY_PSK;
1162         }
1163         if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
1164                 config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
1165             return SECURITY_EAP;
1166         }
1167         return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
1168     }
1169 
securityToString(int security, int pskType)1170     public static String securityToString(int security, int pskType) {
1171         if (security == SECURITY_WEP) {
1172             return "WEP";
1173         } else if (security == SECURITY_PSK) {
1174             if (pskType == PSK_WPA) {
1175                 return "WPA";
1176             } else if (pskType == PSK_WPA2) {
1177                 return "WPA2";
1178             } else if (pskType == PSK_WPA_WPA2) {
1179                 return "WPA_WPA2";
1180             }
1181             return "PSK";
1182         } else if (security == SECURITY_EAP) {
1183             return "EAP";
1184         }
1185         return "NONE";
1186     }
1187 
removeDoubleQuotes(String string)1188     static String removeDoubleQuotes(String string) {
1189         if (TextUtils.isEmpty(string)) {
1190             return "";
1191         }
1192         int length = string.length();
1193         if ((length > 1) && (string.charAt(0) == '"')
1194                 && (string.charAt(length - 1) == '"')) {
1195             return string.substring(1, length - 1);
1196         }
1197         return string;
1198     }
1199 
1200     public interface AccessPointListener {
onAccessPointChanged(AccessPoint accessPoint)1201         void onAccessPointChanged(AccessPoint accessPoint);
onLevelChanged(AccessPoint accessPoint)1202         void onLevelChanged(AccessPoint accessPoint);
1203     }
1204 }
1205