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