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