Home | History | Annotate | Download | only in wifi
      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 
    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 
    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      */
    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 
    294     AccessPoint(Context context, AccessPoint other) {
    295         mContext = context;
    296         copyFrom(other);
    297     }
    298 
    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      */
    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
    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
    386     public boolean equals(Object other) {
    387         if (!(other instanceof AccessPoint)) return false;
    388         return (this.compareTo((AccessPoint) other) == 0);
    389     }
    390 
    391     @Override
    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
    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      */
    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      */
    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      */
    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. */
    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      */
    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 
    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 
    583     public boolean matches(ScanResult result) {
    584         return ssid.equals(result.SSID) && security == getSecurity(result);
    585     }
    586 
    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 
    597     public WifiConfiguration getConfig() {
    598         return mConfig;
    599     }
    600 
    601     public String getPasspointFqdn() {
    602         return mFqdn;
    603     }
    604 
    605     public void clearConfig() {
    606         mConfig = null;
    607         networkId = WifiConfiguration.INVALID_NETWORK_ID;
    608     }
    609 
    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      */
    620     public int getLevel() {
    621         return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
    622     }
    623 
    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      */
    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. */
    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      */
    674     public boolean isMetered() {
    675         return mIsScoredNetworkMetered
    676                 || WifiConfiguration.isMetered(mConfig, mInfo);
    677     }
    678 
    679     public NetworkInfo getNetworkInfo() {
    680         return mNetworkInfo;
    681     }
    682 
    683     public int getSecurity() {
    684         return security;
    685     }
    686 
    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 
    722     public String getSsidStr() {
    723         return ssid;
    724     }
    725 
    726     public String getBssid() {
    727         return bssid;
    728     }
    729 
    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 
    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 
    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 
    755     public boolean isCarrierAp() {
    756         return mIsCarrierAp;
    757     }
    758 
    759     public int getCarrierApEapType() {
    760         return mCarrierApEapType;
    761     }
    762 
    763     public String getCarrierName() {
    764         return mCarrierName;
    765     }
    766 
    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 
    793     public String getSummary() {
    794         return getSettingsSummary(mConfig);
    795     }
    796 
    797     public String getSettingsSummary() {
    798         return getSettingsSummary(mConfig);
    799     }
    800 
    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      */
    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
   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 
   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      */
   1043     public boolean isActive() {
   1044         return mNetworkInfo != null &&
   1045                 (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
   1046                  mNetworkInfo.getState() != State.DISCONNECTED);
   1047     }
   1048 
   1049     public boolean isConnectable() {
   1050         return getLevel() != -1 && getDetailedState() == null;
   1051     }
   1052 
   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      */
   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      */
   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      */
   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 
   1091     public boolean isSaved() {
   1092         return networkId != WifiConfiguration.INVALID_NETWORK_ID;
   1093     }
   1094 
   1095     public Object getTag() {
   1096         return mTag;
   1097     }
   1098 
   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      */
   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 
   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 
   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 
   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 
   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      */
   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. */
   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 
   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
   1265     void setRssi(int rssi) {
   1266         mRssi = rssi;
   1267     }
   1268 
   1269     /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */
   1270     void setUnreachable() {
   1271         setRssi(AccessPoint.UNREACHABLE_RSSI);
   1272     }
   1273 
   1274     int getSpeed() { return mSpeed;}
   1275 
   1276     @Nullable
   1277     String getSpeedLabel() {
   1278         return getSpeedLabel(mSpeed);
   1279     }
   1280 
   1281     @Nullable
   1282     @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
   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. */
   1315     public boolean isReachable() {
   1316         return mRssi != UNREACHABLE_RSSI;
   1317     }
   1318 
   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 
   1376     public static String getSummary(Context context, DetailedState state, boolean isEphemeral) {
   1377         return getSummary(context, null, state, isEphemeral, null);
   1378     }
   1379 
   1380     public static String getSummary(Context context, DetailedState state, boolean isEphemeral,
   1381             String passpointProvider) {
   1382         return getSummary(context, null, state, isEphemeral, passpointProvider);
   1383     }
   1384 
   1385     public static String convertToQuotedString(String string) {
   1386         return "\"" + string + "\"";
   1387     }
   1388 
   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 
   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 
   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 
   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 
   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 {
   1457         void onAccessPointChanged(AccessPoint accessPoint);
   1458         void onLevelChanged(AccessPoint accessPoint);
   1459     }
   1460 }
   1461