• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.IntDef;
20 import android.annotation.Nullable;
21 import android.app.AppGlobals;
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.PackageManager;
26 import android.net.ConnectivityManager;
27 import android.net.NetworkCapabilities;
28 import android.net.NetworkInfo;
29 import android.net.NetworkInfo.DetailedState;
30 import android.net.NetworkInfo.State;
31 import android.net.NetworkKey;
32 import android.net.NetworkScoreManager;
33 import android.net.NetworkScorerAppData;
34 import android.net.ScoredNetwork;
35 import android.net.WifiKey;
36 import android.net.wifi.IWifiManager;
37 import android.net.wifi.ScanResult;
38 import android.net.wifi.WifiConfiguration;
39 import android.net.wifi.WifiConfiguration.KeyMgmt;
40 import android.net.wifi.WifiEnterpriseConfig;
41 import android.net.wifi.WifiInfo;
42 import android.net.wifi.WifiManager;
43 import android.net.wifi.WifiNetworkScoreCache;
44 import android.net.wifi.hotspot2.PasspointConfiguration;
45 import android.os.Bundle;
46 import android.os.RemoteException;
47 import android.os.ServiceManager;
48 import android.os.SystemClock;
49 import android.os.UserHandle;
50 import android.support.annotation.NonNull;
51 import android.text.Spannable;
52 import android.text.SpannableString;
53 import android.text.TextUtils;
54 import android.text.style.TtsSpan;
55 import android.util.Log;
56 
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.settingslib.R;
59 
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.util.ArrayList;
63 import java.util.HashMap;
64 import java.util.Iterator;
65 import java.util.Map;
66 import java.util.concurrent.ConcurrentHashMap;
67 import java.util.concurrent.atomic.AtomicInteger;
68 
69 
70 public class AccessPoint implements Comparable<AccessPoint> {
71     static final String TAG = "SettingsLib.AccessPoint";
72 
73     /**
74      * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
75      */
76     public static final int LOWER_FREQ_24GHZ = 2400;
77 
78     /**
79      * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
80      */
81     public static final int HIGHER_FREQ_24GHZ = 2500;
82 
83     /**
84      * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
85      */
86     public static final int LOWER_FREQ_5GHZ = 4900;
87 
88     /**
89      * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
90      */
91     public static final int HIGHER_FREQ_5GHZ = 5900;
92 
93     @IntDef({Speed.NONE, Speed.SLOW, Speed.MODERATE, Speed.FAST, Speed.VERY_FAST})
94     @Retention(RetentionPolicy.SOURCE)
95     public @interface Speed {
96         /**
97          * Constant value representing an unlabeled / unscored network.
98          */
99         int NONE = 0;
100         /**
101          * Constant value representing a slow speed network connection.
102          */
103         int SLOW = 5;
104         /**
105          * Constant value representing a medium speed network connection.
106          */
107         int MODERATE = 10;
108         /**
109          * Constant value representing a fast speed network connection.
110          */
111         int FAST = 20;
112         /**
113          * Constant value representing a very fast speed network connection.
114          */
115         int VERY_FAST = 30;
116     }
117 
118     /**
119      * Experimental: we should be able to show the user the list of BSSIDs and bands
120      *  for that SSID.
121      *  For now this data is used only with Verbose Logging so as to show the band and number
122      *  of BSSIDs on which that network is seen.
123      */
124     private final ConcurrentHashMap<String, ScanResult> mScanResultCache =
125             new ConcurrentHashMap<String, ScanResult>(32);
126 
127     /**
128      * Map of BSSIDs to scored networks for individual bssids.
129      *
130      * <p>This cache should not be evicted with scan results, as the values here are used to
131      * generate a fallback in the absence of scores for the visible APs.
132      */
133     private final Map<String, TimestampedScoredNetwork> mScoredNetworkCache = new HashMap<>();
134 
135     /** Maximum age of scan results to hold onto while actively scanning. **/
136     private static final long MAX_SCAN_RESULT_AGE_MILLIS = 25000;
137 
138     static final String KEY_NETWORKINFO = "key_networkinfo";
139     static final String KEY_WIFIINFO = "key_wifiinfo";
140     static final String KEY_SCANRESULT = "key_scanresult";
141     static final String KEY_SSID = "key_ssid";
142     static final String KEY_SECURITY = "key_security";
143     static final String KEY_SPEED = "key_speed";
144     static final String KEY_PSKTYPE = "key_psktype";
145     static final String KEY_SCANRESULTCACHE = "key_scanresultcache";
146     static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache";
147     static final String KEY_CONFIG = "key_config";
148     static final String KEY_FQDN = "key_fqdn";
149     static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
150     static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
151     static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
152     static final String KEY_CARRIER_NAME = "key_carrier_name";
153     static final AtomicInteger sLastId = new AtomicInteger(0);
154 
155     /**
156      * These values are matched in string arrays -- changes must be kept in sync
157      */
158     public static final int SECURITY_NONE = 0;
159     public static final int SECURITY_WEP = 1;
160     public static final int SECURITY_PSK = 2;
161     public static final int SECURITY_EAP = 3;
162 
163     private static final int PSK_UNKNOWN = 0;
164     private static final int PSK_WPA = 1;
165     private static final int PSK_WPA2 = 2;
166     private static final int PSK_WPA_WPA2 = 3;
167 
168     /**
169      * The number of distinct wifi levels.
170      *
171      * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
172      */
173     public static final int SIGNAL_LEVELS = 5;
174 
175     public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
176 
177     private final Context mContext;
178 
179     private String ssid;
180     private String bssid;
181     private int security;
182     private int networkId = WifiConfiguration.INVALID_NETWORK_ID;
183 
184     private int pskType = PSK_UNKNOWN;
185 
186     private WifiConfiguration mConfig;
187 
188     private int mRssi = UNREACHABLE_RSSI;
189     private long mSeen = 0;
190 
191     private WifiInfo mInfo;
192     private NetworkInfo mNetworkInfo;
193     AccessPointListener mAccessPointListener;
194 
195     private Object mTag;
196 
197     @Speed private int mSpeed = Speed.NONE;
198     private boolean mIsScoredNetworkMetered = false;
199 
200     // used to co-relate internal vs returned accesspoint.
201     int mId;
202 
203     /**
204      * Information associated with the {@link PasspointConfiguration}.  Only maintaining
205      * the relevant info to preserve spaces.
206      */
207     private String mFqdn;
208     private String mProviderFriendlyName;
209 
210     private boolean mIsCarrierAp = false;
211     /**
212      * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
213      */
214     private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
215     private String mCarrierName = null;
216 
AccessPoint(Context context, Bundle savedState)217     public AccessPoint(Context context, Bundle savedState) {
218         mContext = context;
219         mConfig = savedState.getParcelable(KEY_CONFIG);
220         if (mConfig != null) {
221             loadConfig(mConfig);
222         }
223         if (savedState.containsKey(KEY_SSID)) {
224             ssid = savedState.getString(KEY_SSID);
225         }
226         if (savedState.containsKey(KEY_SECURITY)) {
227             security = savedState.getInt(KEY_SECURITY);
228         }
229         if (savedState.containsKey(KEY_SPEED)) {
230             mSpeed = savedState.getInt(KEY_SPEED);
231         }
232         if (savedState.containsKey(KEY_PSKTYPE)) {
233             pskType = savedState.getInt(KEY_PSKTYPE);
234         }
235         mInfo = savedState.getParcelable(KEY_WIFIINFO);
236         if (savedState.containsKey(KEY_NETWORKINFO)) {
237             mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
238         }
239         if (savedState.containsKey(KEY_SCANRESULTCACHE)) {
240             ArrayList<ScanResult> scanResultArrayList =
241                     savedState.getParcelableArrayList(KEY_SCANRESULTCACHE);
242             mScanResultCache.clear();
243             for (ScanResult result : scanResultArrayList) {
244                 mScanResultCache.put(result.BSSID, result);
245             }
246         }
247         if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) {
248             ArrayList<TimestampedScoredNetwork> scoredNetworkArrayList =
249                     savedState.getParcelableArrayList(KEY_SCOREDNETWORKCACHE);
250             for (TimestampedScoredNetwork timedScore : scoredNetworkArrayList) {
251                 mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore);
252             }
253         }
254         if (savedState.containsKey(KEY_FQDN)) {
255             mFqdn = savedState.getString(KEY_FQDN);
256         }
257         if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
258             mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
259         }
260         if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
261             mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
262         }
263         if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
264             mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
265         }
266         if (savedState.containsKey(KEY_CARRIER_NAME)) {
267             mCarrierName = savedState.getString(KEY_CARRIER_NAME);
268         }
269         update(mConfig, mInfo, mNetworkInfo);
270 
271         // Do not evict old scan results on initial creation
272         updateRssi();
273         updateSeen();
274         mId = sLastId.incrementAndGet();
275     }
276 
AccessPoint(Context context, WifiConfiguration config)277     public AccessPoint(Context context, WifiConfiguration config) {
278         mContext = context;
279         loadConfig(config);
280         mId = sLastId.incrementAndGet();
281     }
282 
283     /**
284      * Initialize an AccessPoint object for a {@link PasspointConfiguration}.  This is mainly
285      * used by "Saved Networks" page for managing the saved {@link PasspointConfiguration}.
286      */
AccessPoint(Context context, PasspointConfiguration config)287     public AccessPoint(Context context, PasspointConfiguration config) {
288         mContext = context;
289         mFqdn = config.getHomeSp().getFqdn();
290         mProviderFriendlyName = config.getHomeSp().getFriendlyName();
291         mId = sLastId.incrementAndGet();
292     }
293 
AccessPoint(Context context, AccessPoint other)294     AccessPoint(Context context, AccessPoint other) {
295         mContext = context;
296         copyFrom(other);
297     }
298 
AccessPoint(Context context, ScanResult result)299     AccessPoint(Context context, ScanResult result) {
300         mContext = context;
301         initWithScanResult(result);
302         mId = sLastId.incrementAndGet();
303     }
304 
305     /**
306      * Copy accesspoint information. NOTE: We do not copy tag information because that is never
307      * set on the internal copy.
308      * @param that
309      */
copyFrom(AccessPoint that)310     void copyFrom(AccessPoint that) {
311         that.evictOldScanResults();
312         this.ssid = that.ssid;
313         this.bssid = that.bssid;
314         this.security = that.security;
315         this.networkId = that.networkId;
316         this.pskType = that.pskType;
317         this.mConfig = that.mConfig; //TODO: Watch out, this object is mutated.
318         this.mRssi = that.mRssi;
319         this.mSeen = that.mSeen;
320         this.mInfo = that.mInfo;
321         this.mNetworkInfo = that.mNetworkInfo;
322         this.mScanResultCache.clear();
323         this.mScanResultCache.putAll(that.mScanResultCache);
324         this.mScoredNetworkCache.clear();
325         this.mScoredNetworkCache.putAll(that.mScoredNetworkCache);
326         this.mId = that.mId;
327         this.mSpeed = that.mSpeed;
328         this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
329         this.mIsCarrierAp = that.mIsCarrierAp;
330         this.mCarrierApEapType = that.mCarrierApEapType;
331         this.mCarrierName = that.mCarrierName;
332     }
333 
334     /**
335     * Returns a negative integer, zero, or a positive integer if this AccessPoint is less than,
336     * equal to, or greater than the other AccessPoint.
337     *
338     * Sort order rules for AccessPoints:
339     *   1. Active before inactive
340     *   2. Reachable before unreachable
341     *   3. Saved before unsaved
342     *   4. Network speed value
343     *   5. Stronger signal before weaker signal
344     *   6. SSID alphabetically
345     *
346     * Note that AccessPoints with a signal are usually also Reachable,
347     * and will thus appear before unreachable saved AccessPoints.
348     */
349     @Override
compareTo(@onNull AccessPoint other)350     public int compareTo(@NonNull AccessPoint other) {
351         // Active one goes first.
352         if (isActive() && !other.isActive()) return -1;
353         if (!isActive() && other.isActive()) return 1;
354 
355         // Reachable one goes before unreachable one.
356         if (isReachable() && !other.isReachable()) return -1;
357         if (!isReachable() && other.isReachable()) return 1;
358 
359         // Configured (saved) one goes before unconfigured one.
360         if (isSaved() && !other.isSaved()) return -1;
361         if (!isSaved() && other.isSaved()) return 1;
362 
363         // Faster speeds go before slower speeds - but only if visible change in speed label
364         if (getSpeed() != other.getSpeed()) {
365             return other.getSpeed() - getSpeed();
366         }
367 
368         // Sort by signal strength, bucketed by level
369         int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS)
370                 - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
371         if (difference != 0) {
372             return difference;
373         }
374 
375         // Sort by ssid.
376         difference = getSsidStr().compareToIgnoreCase(other.getSsidStr());
377         if (difference != 0) {
378             return difference;
379         }
380 
381         // Do a case sensitive comparison to distinguish SSIDs that differ in case only
382         return getSsidStr().compareTo(other.getSsidStr());
383     }
384 
385     @Override
equals(Object other)386     public boolean equals(Object other) {
387         if (!(other instanceof AccessPoint)) return false;
388         return (this.compareTo((AccessPoint) other) == 0);
389     }
390 
391     @Override
hashCode()392     public int hashCode() {
393         int result = 0;
394         if (mInfo != null) result += 13 * mInfo.hashCode();
395         result += 19 * mRssi;
396         result += 23 * networkId;
397         result += 29 * ssid.hashCode();
398         return result;
399     }
400 
401     @Override
toString()402     public String toString() {
403         StringBuilder builder = new StringBuilder().append("AccessPoint(")
404                 .append(ssid);
405         if (bssid != null) {
406             builder.append(":").append(bssid);
407         }
408         if (isSaved()) {
409             builder.append(',').append("saved");
410         }
411         if (isActive()) {
412             builder.append(',').append("active");
413         }
414         if (isEphemeral()) {
415             builder.append(',').append("ephemeral");
416         }
417         if (isConnectable()) {
418             builder.append(',').append("connectable");
419         }
420         if (security != SECURITY_NONE) {
421             builder.append(',').append(securityToString(security, pskType));
422         }
423         builder.append(",level=").append(getLevel());
424         if (mSpeed != Speed.NONE) {
425             builder.append(",speed=").append(mSpeed);
426         }
427         builder.append(",metered=").append(isMetered());
428 
429         if (WifiTracker.sVerboseLogging) {
430             builder.append(",rssi=").append(mRssi);
431             builder.append(",scan cache size=").append(mScanResultCache.size());
432         }
433 
434         return builder.append(')').toString();
435     }
436 
437     /**
438      * Updates the AccessPoint rankingScore, metering, and speed, returning true if the data has
439      * changed.
440      *
441      * @param scoreCache The score cache to use to retrieve scores
442      * @param scoringUiEnabled Whether to show scoring and badging UI
443      * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when
444      *         generating speed labels
445      */
update( WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled, long maxScoreCacheAgeMillis)446     boolean update(
447             WifiNetworkScoreCache scoreCache,
448             boolean scoringUiEnabled,
449             long maxScoreCacheAgeMillis) {
450         boolean scoreChanged = false;
451         if (scoringUiEnabled) {
452             scoreChanged = updateScores(scoreCache, maxScoreCacheAgeMillis);
453         }
454         return updateMetered(scoreCache) || scoreChanged;
455     }
456 
457     /**
458      * Updates the AccessPoint rankingScore and speed, returning true if the data has changed.
459      *
460      * <p>Any cached {@link TimestampedScoredNetwork} objects older than the given max age in millis
461      * will be removed when this method is invoked.
462      *
463      * <p>Precondition: {@link #mRssi} is up to date before invoking this method.
464      *
465      * @param scoreCache The score cache to use to retrieve scores
466      * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when
467      *         generating speed labels
468      *
469      * @return true if the set speed has changed
470      */
updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis)471     private boolean updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis) {
472         long nowMillis = SystemClock.elapsedRealtime();
473         for (ScanResult result : mScanResultCache.values()) {
474             ScoredNetwork score = scoreCache.getScoredNetwork(result);
475             if (score == null) {
476                 continue;
477             }
478             TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
479             if (timedScore == null) {
480                 mScoredNetworkCache.put(
481                         result.BSSID, new TimestampedScoredNetwork(score, nowMillis));
482             } else {
483                 // Update data since the has been seen in the score cache
484                 timedScore.update(score, nowMillis);
485             }
486         }
487 
488         // Remove old cached networks
489         long evictionCutoff = nowMillis - maxScoreCacheAgeMillis;
490         Iterator<TimestampedScoredNetwork> iterator = mScoredNetworkCache.values().iterator();
491         iterator.forEachRemaining(timestampedScoredNetwork -> {
492             if (timestampedScoredNetwork.getUpdatedTimestampMillis() < evictionCutoff) {
493                 iterator.remove();
494             }
495         });
496 
497         return updateSpeed();
498     }
499 
500     /**
501      * Updates the internal speed, returning true if the update resulted in a speed label change.
502      */
updateSpeed()503     private boolean updateSpeed() {
504         int oldSpeed = mSpeed;
505         mSpeed = generateAverageSpeedForSsid();
506 
507         boolean changed = oldSpeed != mSpeed;
508         if(WifiTracker.sVerboseLogging && changed) {
509             Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed));
510         }
511         return changed;
512     }
513 
514     /** Creates a speed value for the current {@link #mRssi} by averaging all non zero badges. */
generateAverageSpeedForSsid()515     @Speed private int generateAverageSpeedForSsid() {
516         if (mScoredNetworkCache.isEmpty()) {
517             return Speed.NONE;
518         }
519 
520         if (Log.isLoggable(TAG, Log.DEBUG)) {
521             Log.d(TAG, String.format("Generating fallbackspeed for %s using cache: %s",
522                     getSsidStr(), mScoredNetworkCache));
523         }
524 
525         // TODO(b/63073866): If flickering issues persist, consider mapping using getLevel rather
526         // than specific rssi value so score doesn't change without a visible wifi bar change. This
527         // issue is likely to be more evident for the active AP whose RSSI value is not half-lifed.
528 
529         int count = 0;
530         int totalSpeed = 0;
531         for (TimestampedScoredNetwork timedScore : mScoredNetworkCache.values()) {
532             int speed = timedScore.getScore().calculateBadge(mRssi);
533             if (speed != Speed.NONE) {
534                 count++;
535                 totalSpeed += speed;
536             }
537         }
538         int speed = count == 0 ? Speed.NONE : totalSpeed / count;
539         if (WifiTracker.sVerboseLogging) {
540             Log.i(TAG, String.format("%s generated fallback speed is: %d", getSsidStr(), speed));
541         }
542         return roundToClosestSpeedEnum(speed);
543     }
544 
545     /**
546      * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning
547      * true if the metering changed.
548      */
updateMetered(WifiNetworkScoreCache scoreCache)549     private boolean updateMetered(WifiNetworkScoreCache scoreCache) {
550         boolean oldMetering = mIsScoredNetworkMetered;
551         mIsScoredNetworkMetered = false;
552 
553         if (isActive() && mInfo != null) {
554             NetworkKey key = new NetworkKey(new WifiKey(
555                     AccessPoint.convertToQuotedString(ssid), mInfo.getBSSID()));
556             ScoredNetwork score = scoreCache.getScoredNetwork(key);
557             if (score != null) {
558                 mIsScoredNetworkMetered |= score.meteredHint;
559             }
560         } else {
561             for (ScanResult result : mScanResultCache.values()) {
562                 ScoredNetwork score = scoreCache.getScoredNetwork(result);
563                 if (score == null) {
564                     continue;
565                 }
566                 mIsScoredNetworkMetered |= score.meteredHint;
567             }
568         }
569         return oldMetering == mIsScoredNetworkMetered;
570     }
571 
evictOldScanResults()572     private void evictOldScanResults() {
573         long nowMs = SystemClock.elapsedRealtime();
574         for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
575             ScanResult result = iter.next();
576             // result timestamp is in microseconds
577             if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) {
578                 iter.remove();
579             }
580         }
581     }
582 
matches(ScanResult result)583     public boolean matches(ScanResult result) {
584         return ssid.equals(result.SSID) && security == getSecurity(result);
585     }
586 
matches(WifiConfiguration config)587     public boolean matches(WifiConfiguration config) {
588         if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) {
589             return ssid.equals(removeDoubleQuotes(config.SSID)) && config.FQDN.equals(mConfig.FQDN);
590         } else {
591             return ssid.equals(removeDoubleQuotes(config.SSID))
592                     && security == getSecurity(config)
593                     && (mConfig == null || mConfig.shared == config.shared);
594         }
595     }
596 
getConfig()597     public WifiConfiguration getConfig() {
598         return mConfig;
599     }
600 
getPasspointFqdn()601     public String getPasspointFqdn() {
602         return mFqdn;
603     }
604 
clearConfig()605     public void clearConfig() {
606         mConfig = null;
607         networkId = WifiConfiguration.INVALID_NETWORK_ID;
608     }
609 
getInfo()610     public WifiInfo getInfo() {
611         return mInfo;
612     }
613 
614     /**
615      * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
616      *
617      * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
618      * always return at least 0.
619      */
getLevel()620     public int getLevel() {
621         return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
622     }
623 
getRssi()624     public int getRssi() {
625         return mRssi;
626     }
627 
628     /**
629      * Updates {@link #mRssi}.
630      *
631      * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned.
632      * If the given AccessPoint is not active, a value will be calculated from previous scan
633      * results, returning the best RSSI for all matching AccessPoints averaged with the previous
634      * value. If the access point is not connected and there are no scan results, the rssi will be
635      * set to {@link #UNREACHABLE_RSSI}.
636      */
updateRssi()637     private void updateRssi() {
638         if (this.isActive()) {
639             return;
640         }
641 
642         int rssi = UNREACHABLE_RSSI;
643         for (ScanResult result : mScanResultCache.values()) {
644             if (result.level > rssi) {
645                 rssi = result.level;
646             }
647         }
648 
649         if (rssi != UNREACHABLE_RSSI && mRssi != UNREACHABLE_RSSI) {
650             mRssi = (mRssi + rssi) / 2; // half-life previous value
651         } else {
652             mRssi = rssi;
653         }
654     }
655 
656     /** Updates {@link #mSeen} based on the scan result cache. */
updateSeen()657     private void updateSeen() {
658         long seen = 0;
659         for (ScanResult result : mScanResultCache.values()) {
660             if (result.timestamp > seen) {
661                 seen = result.timestamp;
662             }
663         }
664 
665         // Only replace the previous value if we have a recent scan result to use
666         if (seen != 0) {
667             mSeen = seen;
668         }
669     }
670 
671     /**
672      * Returns if the network should be considered metered.
673      */
isMetered()674     public boolean isMetered() {
675         return mIsScoredNetworkMetered
676                 || WifiConfiguration.isMetered(mConfig, mInfo);
677     }
678 
getNetworkInfo()679     public NetworkInfo getNetworkInfo() {
680         return mNetworkInfo;
681     }
682 
getSecurity()683     public int getSecurity() {
684         return security;
685     }
686 
getSecurityString(boolean concise)687     public String getSecurityString(boolean concise) {
688         Context context = mContext;
689         if (isPasspoint() || isPasspointConfig()) {
690             return concise ? context.getString(R.string.wifi_security_short_eap) :
691                 context.getString(R.string.wifi_security_eap);
692         }
693         switch(security) {
694             case SECURITY_EAP:
695                 return concise ? context.getString(R.string.wifi_security_short_eap) :
696                     context.getString(R.string.wifi_security_eap);
697             case SECURITY_PSK:
698                 switch (pskType) {
699                     case PSK_WPA:
700                         return concise ? context.getString(R.string.wifi_security_short_wpa) :
701                             context.getString(R.string.wifi_security_wpa);
702                     case PSK_WPA2:
703                         return concise ? context.getString(R.string.wifi_security_short_wpa2) :
704                             context.getString(R.string.wifi_security_wpa2);
705                     case PSK_WPA_WPA2:
706                         return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
707                             context.getString(R.string.wifi_security_wpa_wpa2);
708                     case PSK_UNKNOWN:
709                     default:
710                         return concise ? context.getString(R.string.wifi_security_short_psk_generic)
711                                 : context.getString(R.string.wifi_security_psk_generic);
712                 }
713             case SECURITY_WEP:
714                 return concise ? context.getString(R.string.wifi_security_short_wep) :
715                     context.getString(R.string.wifi_security_wep);
716             case SECURITY_NONE:
717             default:
718                 return concise ? "" : context.getString(R.string.wifi_security_none);
719         }
720     }
721 
getSsidStr()722     public String getSsidStr() {
723         return ssid;
724     }
725 
getBssid()726     public String getBssid() {
727         return bssid;
728     }
729 
getSsid()730     public CharSequence getSsid() {
731         final SpannableString str = new SpannableString(ssid);
732         str.setSpan(new TtsSpan.TelephoneBuilder(ssid).build(), 0, ssid.length(),
733                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);
734         return str;
735     }
736 
getConfigName()737     public String getConfigName() {
738         if (mConfig != null && mConfig.isPasspoint()) {
739             return mConfig.providerFriendlyName;
740         } else if (mFqdn != null) {
741             return mProviderFriendlyName;
742         } else {
743             return ssid;
744         }
745     }
746 
getDetailedState()747     public DetailedState getDetailedState() {
748         if (mNetworkInfo != null) {
749             return mNetworkInfo.getDetailedState();
750         }
751         Log.w(TAG, "NetworkInfo is null, cannot return detailed state");
752         return null;
753     }
754 
isCarrierAp()755     public boolean isCarrierAp() {
756         return mIsCarrierAp;
757     }
758 
getCarrierApEapType()759     public int getCarrierApEapType() {
760         return mCarrierApEapType;
761     }
762 
getCarrierName()763     public String getCarrierName() {
764         return mCarrierName;
765     }
766 
getSavedNetworkSummary()767     public String getSavedNetworkSummary() {
768         WifiConfiguration config = mConfig;
769         if (config != null) {
770             PackageManager pm = mContext.getPackageManager();
771             String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID);
772             int userId = UserHandle.getUserId(config.creatorUid);
773             ApplicationInfo appInfo = null;
774             if (config.creatorName != null && config.creatorName.equals(systemName)) {
775                 appInfo = mContext.getApplicationInfo();
776             } else {
777                 try {
778                     IPackageManager ipm = AppGlobals.getPackageManager();
779                     appInfo = ipm.getApplicationInfo(config.creatorName, 0 /* flags */, userId);
780                 } catch (RemoteException rex) {
781                 }
782             }
783             if (appInfo != null &&
784                     !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) &&
785                     !appInfo.packageName.equals(
786                     mContext.getString(R.string.certinstaller_package))) {
787                 return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm));
788             }
789         }
790         return "";
791     }
792 
getSummary()793     public String getSummary() {
794         return getSettingsSummary(mConfig);
795     }
796 
getSettingsSummary()797     public String getSettingsSummary() {
798         return getSettingsSummary(mConfig);
799     }
800 
getSettingsSummary(WifiConfiguration config)801     private String getSettingsSummary(WifiConfiguration config) {
802         // Update to new summary
803         StringBuilder summary = new StringBuilder();
804 
805         if (isActive() && config != null && config.isPasspoint()) {
806             // This is the active connection on passpoint
807             summary.append(getSummary(mContext, getDetailedState(),
808                     false, config.providerFriendlyName));
809         } else if (isActive() && config != null && getDetailedState() == DetailedState.CONNECTED
810                 && mIsCarrierAp) {
811             summary.append(String.format(mContext.getString(R.string.connected_via_carrier), mCarrierName));
812         } else if (isActive()) {
813             // This is the active connection on non-passpoint network
814             summary.append(getSummary(mContext, getDetailedState(),
815                     mInfo != null && mInfo.isEphemeral()));
816         } else if (config != null && config.isPasspoint()
817                 && config.getNetworkSelectionStatus().isNetworkEnabled()) {
818             String format = mContext.getString(R.string.available_via_passpoint);
819             summary.append(String.format(format, config.providerFriendlyName));
820         } else if (config != null && config.hasNoInternetAccess()) {
821             int messageID = config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
822                     ? R.string.wifi_no_internet_no_reconnect
823                     : R.string.wifi_no_internet;
824             summary.append(mContext.getString(messageID));
825         } else if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
826             WifiConfiguration.NetworkSelectionStatus networkStatus =
827                     config.getNetworkSelectionStatus();
828             switch (networkStatus.getNetworkSelectionDisableReason()) {
829                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
830                     summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
831                     break;
832                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
833                     summary.append(mContext.getString(R.string.wifi_check_password_try_again));
834                     break;
835                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
836                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE:
837                     summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
838                     break;
839                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
840                     summary.append(mContext.getString(R.string.wifi_disabled_generic));
841                     break;
842             }
843         } else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
844             summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
845         } else if (mIsCarrierAp) {
846             summary.append(String.format(mContext.getString(R.string.available_via_carrier), mCarrierName));
847         } else if (!isReachable()) { // Wifi out of range
848             summary.append(mContext.getString(R.string.wifi_not_in_range));
849         } else { // In range, not disabled.
850             if (config != null) { // Is saved network
851                 // Last attempt to connect to this failed. Show reason why
852                 switch (config.recentFailure.getAssociationStatus()) {
853                     case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA:
854                         summary.append(mContext.getString(
855                                 R.string.wifi_ap_unable_to_handle_new_sta));
856                         break;
857                     default:
858                         // "Saved"
859                         summary.append(mContext.getString(R.string.wifi_remembered));
860                         break;
861                 }
862             }
863         }
864 
865         if (WifiTracker.sVerboseLogging) {
866             // Add RSSI/band information for this config, what was seen up to 6 seconds ago
867             // verbose WiFi Logging is only turned on thru developers settings
868             if (isActive() && mInfo != null) {
869                 summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
870             }
871             summary.append(" " + getVisibilityStatus());
872             if (config != null && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
873                 summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
874                 if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
875                     long now = System.currentTimeMillis();
876                     long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
877                     long sec = diff%60; //seconds
878                     long min = (diff/60)%60; //minutes
879                     long hour = (min/60)%60; //hours
880                     summary.append(", ");
881                     if (hour > 0) summary.append(Long.toString(hour) + "h ");
882                     summary.append( Long.toString(min) + "m ");
883                     summary.append( Long.toString(sec) + "s ");
884                 }
885                 summary.append(")");
886             }
887 
888             if (config != null) {
889                 WifiConfiguration.NetworkSelectionStatus networkStatus =
890                         config.getNetworkSelectionStatus();
891                 for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
892                         index < WifiConfiguration.NetworkSelectionStatus
893                         .NETWORK_SELECTION_DISABLED_MAX; index++) {
894                     if (networkStatus.getDisableReasonCounter(index) != 0) {
895                         summary.append(" " + WifiConfiguration.NetworkSelectionStatus
896                                 .getNetworkDisableReasonString(index) + "="
897                                 + networkStatus.getDisableReasonCounter(index));
898                     }
899                 }
900             }
901         }
902 
903         // If Speed label and summary are both present, use the preference combination to combine
904         // the two, else return the non-null one.
905         if (getSpeedLabel() != null && summary.length() != 0) {
906             return mContext.getResources().getString(
907                     R.string.preference_summary_default_combination,
908                     getSpeedLabel(),
909                     summary.toString());
910         } else if (getSpeedLabel() != null) {
911             return getSpeedLabel();
912         } else {
913             return summary.toString();
914         }
915     }
916 
917     /**
918      * Returns the visibility status of the WifiConfiguration.
919      *
920      * @return autojoin debugging information
921      * TODO: use a string formatter
922      * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
923      * For instance [-40,5/-30,2]
924      */
getVisibilityStatus()925     private String getVisibilityStatus() {
926         StringBuilder visibility = new StringBuilder();
927         StringBuilder scans24GHz = new StringBuilder();
928         StringBuilder scans5GHz = new StringBuilder();
929         String bssid = null;
930 
931         long now = System.currentTimeMillis();
932 
933         if (isActive() && mInfo != null) {
934             bssid = mInfo.getBSSID();
935             if (bssid != null) {
936                 visibility.append(" ").append(bssid);
937             }
938             visibility.append(" rssi=").append(mInfo.getRssi());
939             visibility.append(" ");
940             visibility.append(" score=").append(mInfo.score);
941             if (mSpeed != Speed.NONE) {
942                 visibility.append(" speed=").append(getSpeedLabel());
943             }
944             visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
945             visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
946             visibility.append(String.format("%.1f ", mInfo.txBadRate));
947             visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
948         }
949 
950         int maxRssi5 = WifiConfiguration.INVALID_RSSI;
951         int maxRssi24 = WifiConfiguration.INVALID_RSSI;
952         final int maxDisplayedScans = 4;
953         int num5 = 0; // number of scanned BSSID on 5GHz band
954         int num24 = 0; // number of scanned BSSID on 2.4Ghz band
955         int numBlackListed = 0;
956         evictOldScanResults();
957 
958         // TODO: sort list by RSSI or age
959         long nowMs = SystemClock.elapsedRealtime();
960         for (ScanResult result : mScanResultCache.values()) {
961             if (result.frequency >= LOWER_FREQ_5GHZ
962                     && result.frequency <= HIGHER_FREQ_5GHZ) {
963                 // Strictly speaking: [4915, 5825]
964                 num5++;
965 
966                 if (result.level > maxRssi5) {
967                     maxRssi5 = result.level;
968                 }
969                 if (num5 <= maxDisplayedScans) {
970                     scans5GHz.append(verboseScanResultSummary(result, bssid, nowMs));
971                 }
972             } else if (result.frequency >= LOWER_FREQ_24GHZ
973                     && result.frequency <= HIGHER_FREQ_24GHZ) {
974                 // Strictly speaking: [2412, 2482]
975                 num24++;
976 
977                 if (result.level > maxRssi24) {
978                     maxRssi24 = result.level;
979                 }
980                 if (num24 <= maxDisplayedScans) {
981                     scans24GHz.append(verboseScanResultSummary(result, bssid, nowMs));
982                 }
983             }
984         }
985         visibility.append(" [");
986         if (num24 > 0) {
987             visibility.append("(").append(num24).append(")");
988             if (num24 > maxDisplayedScans) {
989                 visibility.append("max=").append(maxRssi24).append(",");
990             }
991             visibility.append(scans24GHz.toString());
992         }
993         visibility.append(";");
994         if (num5 > 0) {
995             visibility.append("(").append(num5).append(")");
996             if (num5 > maxDisplayedScans) {
997                 visibility.append("max=").append(maxRssi5).append(",");
998             }
999             visibility.append(scans5GHz.toString());
1000         }
1001         if (numBlackListed > 0)
1002             visibility.append("!").append(numBlackListed);
1003         visibility.append("]");
1004 
1005         return visibility.toString();
1006     }
1007 
1008     @VisibleForTesting
verboseScanResultSummary(ScanResult result, String bssid, long nowMs)1009     /* package */ String verboseScanResultSummary(ScanResult result, String bssid, long nowMs) {
1010         StringBuilder stringBuilder = new StringBuilder();
1011         stringBuilder.append(" \n{").append(result.BSSID);
1012         if (result.BSSID.equals(bssid)) {
1013             stringBuilder.append("*");
1014         }
1015         stringBuilder.append("=").append(result.frequency);
1016         stringBuilder.append(",").append(result.level);
1017         int speed = getSpecificApSpeed(result);
1018         if (speed != Speed.NONE) {
1019             stringBuilder.append(",")
1020                     .append(getSpeedLabel(speed));
1021         }
1022         int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
1023         stringBuilder.append(",").append(ageSeconds).append("s");
1024         stringBuilder.append("}");
1025         return stringBuilder.toString();
1026     }
1027 
getSpecificApSpeed(ScanResult result)1028     @Speed private int getSpecificApSpeed(ScanResult result) {
1029         TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID);
1030         if (timedScore == null) {
1031             return Speed.NONE;
1032         }
1033         // For debugging purposes we may want to use mRssi rather than result.level as the average
1034         // speed wil be determined by mRssi
1035         return timedScore.getScore().calculateBadge(result.level);
1036     }
1037 
1038     /**
1039      * Return whether this is the active connection.
1040      * For ephemeral connections (networkId is invalid), this returns false if the network is
1041      * disconnected.
1042      */
isActive()1043     public boolean isActive() {
1044         return mNetworkInfo != null &&
1045                 (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
1046                  mNetworkInfo.getState() != State.DISCONNECTED);
1047     }
1048 
isConnectable()1049     public boolean isConnectable() {
1050         return getLevel() != -1 && getDetailedState() == null;
1051     }
1052 
isEphemeral()1053     public boolean isEphemeral() {
1054         return mInfo != null && mInfo.isEphemeral() &&
1055                 mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED;
1056     }
1057 
1058     /**
1059      * Return true if this AccessPoint represents a Passpoint AP.
1060      */
isPasspoint()1061     public boolean isPasspoint() {
1062         return mConfig != null && mConfig.isPasspoint();
1063     }
1064 
1065     /**
1066      * Return true if this AccessPoint represents a Passpoint provider configuration.
1067      */
isPasspointConfig()1068     public boolean isPasspointConfig() {
1069         return mFqdn != null;
1070     }
1071 
1072     /**
1073      * Return whether the given {@link WifiInfo} is for this access point.
1074      * If the current AP does not have a network Id then the config is used to
1075      * match based on SSID and security.
1076      */
isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info)1077     private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
1078         if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
1079             return networkId == info.getNetworkId();
1080         } else if (config != null) {
1081             return matches(config);
1082         }
1083         else {
1084             // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
1085             // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
1086             // TODO: Handle hex string SSIDs.
1087             return ssid.equals(removeDoubleQuotes(info.getSSID()));
1088         }
1089     }
1090 
isSaved()1091     public boolean isSaved() {
1092         return networkId != WifiConfiguration.INVALID_NETWORK_ID;
1093     }
1094 
getTag()1095     public Object getTag() {
1096         return mTag;
1097     }
1098 
setTag(Object tag)1099     public void setTag(Object tag) {
1100         mTag = tag;
1101     }
1102 
1103     /**
1104      * Generate and save a default wifiConfiguration with common values.
1105      * Can only be called for unsecured networks.
1106      */
generateOpenNetworkConfig()1107     public void generateOpenNetworkConfig() {
1108         if (security != SECURITY_NONE)
1109             throw new IllegalStateException();
1110         if (mConfig != null)
1111             return;
1112         mConfig = new WifiConfiguration();
1113         mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
1114         mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
1115     }
1116 
loadConfig(WifiConfiguration config)1117     void loadConfig(WifiConfiguration config) {
1118         ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
1119         bssid = config.BSSID;
1120         security = getSecurity(config);
1121         networkId = config.networkId;
1122         mConfig = config;
1123     }
1124 
initWithScanResult(ScanResult result)1125     private void initWithScanResult(ScanResult result) {
1126         ssid = result.SSID;
1127         bssid = result.BSSID;
1128         security = getSecurity(result);
1129         if (security == SECURITY_PSK)
1130             pskType = getPskType(result);
1131 
1132         mScanResultCache.put(result.BSSID, result);
1133         updateRssi();
1134         mSeen = result.timestamp; // even if the timestamp is old it is still valid
1135         mIsCarrierAp = result.isCarrierAp;
1136         mCarrierApEapType = result.carrierApEapType;
1137         mCarrierName = result.carrierName;
1138     }
1139 
saveWifiState(Bundle savedState)1140     public void saveWifiState(Bundle savedState) {
1141         if (ssid != null) savedState.putString(KEY_SSID, getSsidStr());
1142         savedState.putInt(KEY_SECURITY, security);
1143         savedState.putInt(KEY_SPEED, mSpeed);
1144         savedState.putInt(KEY_PSKTYPE, pskType);
1145         if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
1146         savedState.putParcelable(KEY_WIFIINFO, mInfo);
1147         evictOldScanResults();
1148         savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
1149                 new ArrayList<ScanResult>(mScanResultCache.values()));
1150         savedState.putParcelableArrayList(KEY_SCOREDNETWORKCACHE,
1151                 new ArrayList<>(mScoredNetworkCache.values()));
1152         if (mNetworkInfo != null) {
1153             savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
1154         }
1155         if (mFqdn != null) {
1156             savedState.putString(KEY_FQDN, mFqdn);
1157         }
1158         if (mProviderFriendlyName != null) {
1159             savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
1160         }
1161         savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
1162         savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
1163         savedState.putString(KEY_CARRIER_NAME, mCarrierName);
1164     }
1165 
setListener(AccessPointListener listener)1166     public void setListener(AccessPointListener listener) {
1167         mAccessPointListener = listener;
1168     }
1169 
1170     /**
1171      * Update the AP with the given scan result.
1172      *
1173      * @param result the ScanResult to add to the AccessPoint scan cache
1174      * @param evictOldScanResults whether stale scan results should be removed
1175      *         from the cache during this update process
1176      * @return true if the scan result update caused a change in state which would impact ranking
1177      *     or AccessPoint rendering (e.g. wifi level, security)
1178      */
update(ScanResult result, boolean evictOldScanResults)1179     boolean update(ScanResult result, boolean evictOldScanResults) {
1180         if (matches(result)) {
1181             int oldLevel = getLevel();
1182 
1183             /* Add or update the scan result for the BSSID */
1184             mScanResultCache.put(result.BSSID, result);
1185             if (evictOldScanResults) evictOldScanResults();
1186             updateSeen();
1187             updateRssi();
1188             int newLevel = getLevel();
1189 
1190             if (newLevel > 0 && newLevel != oldLevel) {
1191                 // Only update labels on visible rssi changes
1192                 updateSpeed();
1193                 if (mAccessPointListener != null) {
1194                     mAccessPointListener.onLevelChanged(this);
1195                 }
1196             }
1197             // This flag only comes from scans, is not easily saved in config
1198             if (security == SECURITY_PSK) {
1199                 pskType = getPskType(result);
1200             }
1201 
1202             if (mAccessPointListener != null) {
1203                 mAccessPointListener.onAccessPointChanged(this);
1204             }
1205 
1206             // The carrier info in the ScanResult is set by the platform based on the SSID and will
1207             // always be the same for all matching scan results.
1208             mIsCarrierAp = result.isCarrierAp;
1209             mCarrierApEapType = result.carrierApEapType;
1210             mCarrierName = result.carrierName;
1211 
1212             return true;
1213         }
1214         return false;
1215     }
1216 
1217     /** Attempt to update the AccessPoint and return true if an update occurred. */
update( @ullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo)1218     public boolean update(
1219             @Nullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
1220 
1221         boolean updated = false;
1222         final int oldLevel = getLevel();
1223         if (info != null && isInfoForThisAccessPoint(config, info)) {
1224             updated = (mInfo == null);
1225             if (mConfig != config) {
1226                 // We do not set updated = true as we do not want to increase the amount of sorting
1227                 // and copying performed in WifiTracker at this time. If issues involving refresh
1228                 // are still seen, we will investigate further.
1229                 update(config); // Notifies the AccessPointListener of the change
1230             }
1231             if (mRssi != info.getRssi() && info.getRssi() != WifiInfo.INVALID_RSSI) {
1232                 mRssi = info.getRssi();
1233                 updated = true;
1234             } else if (mNetworkInfo != null && networkInfo != null
1235                     && mNetworkInfo.getDetailedState() != networkInfo.getDetailedState()) {
1236                 updated = true;
1237             }
1238             mInfo = info;
1239             mNetworkInfo = networkInfo;
1240         } else if (mInfo != null) {
1241             updated = true;
1242             mInfo = null;
1243             mNetworkInfo = null;
1244         }
1245         if (updated && mAccessPointListener != null) {
1246             mAccessPointListener.onAccessPointChanged(this);
1247 
1248             if (oldLevel != getLevel() /* current level */) {
1249                 mAccessPointListener.onLevelChanged(this);
1250             }
1251         }
1252 
1253         return updated;
1254     }
1255 
update(@ullable WifiConfiguration config)1256     void update(@Nullable WifiConfiguration config) {
1257         mConfig = config;
1258         networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID;
1259         if (mAccessPointListener != null) {
1260             mAccessPointListener.onAccessPointChanged(this);
1261         }
1262     }
1263 
1264     @VisibleForTesting
setRssi(int rssi)1265     void setRssi(int rssi) {
1266         mRssi = rssi;
1267     }
1268 
1269     /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */
setUnreachable()1270     void setUnreachable() {
1271         setRssi(AccessPoint.UNREACHABLE_RSSI);
1272     }
1273 
getSpeed()1274     int getSpeed() { return mSpeed;}
1275 
1276     @Nullable
getSpeedLabel()1277     String getSpeedLabel() {
1278         return getSpeedLabel(mSpeed);
1279     }
1280 
1281     @Nullable
1282     @Speed
roundToClosestSpeedEnum(int speed)1283     private int roundToClosestSpeedEnum(int speed) {
1284         if (speed < Speed.SLOW) {
1285             return Speed.NONE;
1286         } else if (speed < (Speed.SLOW + Speed.MODERATE) / 2) {
1287             return Speed.SLOW;
1288         } else if (speed < (Speed.MODERATE + Speed.FAST) / 2) {
1289             return Speed.MODERATE;
1290         } else if (speed < (Speed.FAST + Speed.VERY_FAST) / 2) {
1291             return Speed.FAST;
1292         } else {
1293             return Speed.VERY_FAST;
1294         }
1295     }
1296 
1297     @Nullable
getSpeedLabel(@peed int speed)1298     private String getSpeedLabel(@Speed int speed) {
1299         switch (speed) {
1300             case Speed.VERY_FAST:
1301                 return mContext.getString(R.string.speed_label_very_fast);
1302             case Speed.FAST:
1303                 return mContext.getString(R.string.speed_label_fast);
1304             case Speed.MODERATE:
1305                 return mContext.getString(R.string.speed_label_okay);
1306             case Speed.SLOW:
1307                 return mContext.getString(R.string.speed_label_slow);
1308             case Speed.NONE:
1309             default:
1310                 return null;
1311         }
1312     }
1313 
1314     /** Return true if the current RSSI is reachable, and false otherwise. */
isReachable()1315     public boolean isReachable() {
1316         return mRssi != UNREACHABLE_RSSI;
1317     }
1318 
getSummary(Context context, String ssid, DetailedState state, boolean isEphemeral, String passpointProvider)1319     public static String getSummary(Context context, String ssid, DetailedState state,
1320             boolean isEphemeral, String passpointProvider) {
1321         if (state == DetailedState.CONNECTED && ssid == null) {
1322             if (TextUtils.isEmpty(passpointProvider) == false) {
1323                 // Special case for connected + passpoint networks.
1324                 String format = context.getString(R.string.connected_via_passpoint);
1325                 return String.format(format, passpointProvider);
1326             } else if (isEphemeral) {
1327                 // Special case for connected + ephemeral networks.
1328                 final NetworkScoreManager networkScoreManager = context.getSystemService(
1329                         NetworkScoreManager.class);
1330                 NetworkScorerAppData scorer = networkScoreManager.getActiveScorer();
1331                 if (scorer != null && scorer.getRecommendationServiceLabel() != null) {
1332                     String format = context.getString(R.string.connected_via_network_scorer);
1333                     return String.format(format, scorer.getRecommendationServiceLabel());
1334                 } else {
1335                     return context.getString(R.string.connected_via_network_scorer_default);
1336                 }
1337             }
1338         }
1339 
1340         // Case when there is wifi connected without internet connectivity.
1341         final ConnectivityManager cm = (ConnectivityManager)
1342                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
1343         if (state == DetailedState.CONNECTED) {
1344             IWifiManager wifiManager = IWifiManager.Stub.asInterface(
1345                     ServiceManager.getService(Context.WIFI_SERVICE));
1346             NetworkCapabilities nc = null;
1347 
1348             try {
1349                 nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork());
1350             } catch (RemoteException e) {}
1351 
1352             if (nc != null) {
1353                 if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) {
1354                     int id = context.getResources()
1355                             .getIdentifier("network_available_sign_in", "string", "android");
1356                     return context.getString(id);
1357                 } else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
1358                     return context.getString(R.string.wifi_connected_no_internet);
1359                 }
1360             }
1361         }
1362         if (state == null) {
1363             Log.w(TAG, "state is null, returning empty summary");
1364             return "";
1365         }
1366         String[] formats = context.getResources().getStringArray((ssid == null)
1367                 ? R.array.wifi_status : R.array.wifi_status_with_ssid);
1368         int index = state.ordinal();
1369 
1370         if (index >= formats.length || formats[index].length() == 0) {
1371             return "";
1372         }
1373         return String.format(formats[index], ssid);
1374     }
1375 
getSummary(Context context, DetailedState state, boolean isEphemeral)1376     public static String getSummary(Context context, DetailedState state, boolean isEphemeral) {
1377         return getSummary(context, null, state, isEphemeral, null);
1378     }
1379 
getSummary(Context context, DetailedState state, boolean isEphemeral, String passpointProvider)1380     public static String getSummary(Context context, DetailedState state, boolean isEphemeral,
1381             String passpointProvider) {
1382         return getSummary(context, null, state, isEphemeral, passpointProvider);
1383     }
1384 
convertToQuotedString(String string)1385     public static String convertToQuotedString(String string) {
1386         return "\"" + string + "\"";
1387     }
1388 
getPskType(ScanResult result)1389     private static int getPskType(ScanResult result) {
1390         boolean wpa = result.capabilities.contains("WPA-PSK");
1391         boolean wpa2 = result.capabilities.contains("WPA2-PSK");
1392         if (wpa2 && wpa) {
1393             return PSK_WPA_WPA2;
1394         } else if (wpa2) {
1395             return PSK_WPA2;
1396         } else if (wpa) {
1397             return PSK_WPA;
1398         } else {
1399             Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
1400             return PSK_UNKNOWN;
1401         }
1402     }
1403 
getSecurity(ScanResult result)1404     private static int getSecurity(ScanResult result) {
1405         if (result.capabilities.contains("WEP")) {
1406             return SECURITY_WEP;
1407         } else if (result.capabilities.contains("PSK")) {
1408             return SECURITY_PSK;
1409         } else if (result.capabilities.contains("EAP")) {
1410             return SECURITY_EAP;
1411         }
1412         return SECURITY_NONE;
1413     }
1414 
getSecurity(WifiConfiguration config)1415     static int getSecurity(WifiConfiguration config) {
1416         if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
1417             return SECURITY_PSK;
1418         }
1419         if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
1420                 config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
1421             return SECURITY_EAP;
1422         }
1423         return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
1424     }
1425 
securityToString(int security, int pskType)1426     public static String securityToString(int security, int pskType) {
1427         if (security == SECURITY_WEP) {
1428             return "WEP";
1429         } else if (security == SECURITY_PSK) {
1430             if (pskType == PSK_WPA) {
1431                 return "WPA";
1432             } else if (pskType == PSK_WPA2) {
1433                 return "WPA2";
1434             } else if (pskType == PSK_WPA_WPA2) {
1435                 return "WPA_WPA2";
1436             }
1437             return "PSK";
1438         } else if (security == SECURITY_EAP) {
1439             return "EAP";
1440         }
1441         return "NONE";
1442     }
1443 
removeDoubleQuotes(String string)1444     static String removeDoubleQuotes(String string) {
1445         if (TextUtils.isEmpty(string)) {
1446             return "";
1447         }
1448         int length = string.length();
1449         if ((length > 1) && (string.charAt(0) == '"')
1450                 && (string.charAt(length - 1) == '"')) {
1451             return string.substring(1, length - 1);
1452         }
1453         return string;
1454     }
1455 
1456     public interface AccessPointListener {
onAccessPointChanged(AccessPoint accessPoint)1457         void onAccessPointChanged(AccessPoint accessPoint);
onLevelChanged(AccessPoint accessPoint)1458         void onLevelChanged(AccessPoint accessPoint);
1459     }
1460 }
1461