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.Network;
     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.wifi.IWifiManager;
     31 import android.net.wifi.ScanResult;
     32 import android.net.wifi.WifiConfiguration;
     33 import android.net.wifi.WifiConfiguration.KeyMgmt;
     34 import android.net.wifi.WifiInfo;
     35 import android.net.wifi.WifiManager;
     36 import android.os.Bundle;
     37 import android.os.RemoteException;
     38 import android.os.ServiceManager;
     39 import android.os.UserHandle;
     40 import android.text.Spannable;
     41 import android.text.SpannableString;
     42 import android.text.TextUtils;
     43 import android.text.style.TtsSpan;
     44 import android.util.Log;
     45 import android.util.LruCache;
     46 
     47 import com.android.settingslib.R;
     48 
     49 import java.util.ArrayList;
     50 import java.util.Map;
     51 
     52 
     53 public class AccessPoint implements Comparable<AccessPoint> {
     54     static final String TAG = "SettingsLib.AccessPoint";
     55 
     56     /**
     57      * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels
     58      */
     59     public static final int LOWER_FREQ_24GHZ = 2400;
     60 
     61     /**
     62      * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels
     63      */
     64     public static final int HIGHER_FREQ_24GHZ = 2500;
     65 
     66     /**
     67      * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
     68      */
     69     public static final int LOWER_FREQ_5GHZ = 4900;
     70 
     71     /**
     72      * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels
     73      */
     74     public static final int HIGHER_FREQ_5GHZ = 5900;
     75 
     76 
     77     /**
     78      * Experimental: we should be able to show the user the list of BSSIDs and bands
     79      *  for that SSID.
     80      *  For now this data is used only with Verbose Logging so as to show the band and number
     81      *  of BSSIDs on which that network is seen.
     82      */
     83     public LruCache<String, ScanResult> mScanResultCache = new LruCache<String, ScanResult>(32);
     84 
     85     private static final String KEY_NETWORKINFO = "key_networkinfo";
     86     private static final String KEY_WIFIINFO = "key_wifiinfo";
     87     private static final String KEY_SCANRESULT = "key_scanresult";
     88     private static final String KEY_SSID = "key_ssid";
     89     private static final String KEY_SECURITY = "key_security";
     90     private static final String KEY_PSKTYPE = "key_psktype";
     91     private static final String KEY_SCANRESULTCACHE = "key_scanresultcache";
     92     private static final String KEY_CONFIG = "key_config";
     93 
     94     /**
     95      * These values are matched in string arrays -- changes must be kept in sync
     96      */
     97     public static final int SECURITY_NONE = 0;
     98     public static final int SECURITY_WEP = 1;
     99     public static final int SECURITY_PSK = 2;
    100     public static final int SECURITY_EAP = 3;
    101 
    102     private static final int PSK_UNKNOWN = 0;
    103     private static final int PSK_WPA = 1;
    104     private static final int PSK_WPA2 = 2;
    105     private static final int PSK_WPA_WPA2 = 3;
    106 
    107     private static final int VISIBILITY_OUTDATED_AGE_IN_MILLI = 20000;
    108     private final Context mContext;
    109 
    110     private String ssid;
    111     private int security;
    112     private int networkId = WifiConfiguration.INVALID_NETWORK_ID;
    113 
    114     private int pskType = PSK_UNKNOWN;
    115 
    116     private WifiConfiguration mConfig;
    117 
    118     private int mRssi = Integer.MAX_VALUE;
    119     private long mSeen = 0;
    120 
    121     private WifiInfo mInfo;
    122     private NetworkInfo mNetworkInfo;
    123     private AccessPointListener mAccessPointListener;
    124 
    125     private Object mTag;
    126 
    127     public AccessPoint(Context context, Bundle savedState) {
    128         mContext = context;
    129         mConfig = savedState.getParcelable(KEY_CONFIG);
    130         if (mConfig != null) {
    131             loadConfig(mConfig);
    132         }
    133         if (savedState.containsKey(KEY_SSID)) {
    134             ssid = savedState.getString(KEY_SSID);
    135         }
    136         if (savedState.containsKey(KEY_SECURITY)) {
    137             security = savedState.getInt(KEY_SECURITY);
    138         }
    139         if (savedState.containsKey(KEY_PSKTYPE)) {
    140             pskType = savedState.getInt(KEY_PSKTYPE);
    141         }
    142         mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
    143         if (savedState.containsKey(KEY_NETWORKINFO)) {
    144             mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
    145         }
    146         if (savedState.containsKey(KEY_SCANRESULTCACHE)) {
    147             ArrayList<ScanResult> scanResultArrayList =
    148                     savedState.getParcelableArrayList(KEY_SCANRESULTCACHE);
    149             mScanResultCache.evictAll();
    150             for (ScanResult result : scanResultArrayList) {
    151                 mScanResultCache.put(result.BSSID, result);
    152             }
    153         }
    154         update(mConfig, mInfo, mNetworkInfo);
    155         mRssi = getRssi();
    156         mSeen = getSeen();
    157     }
    158 
    159     AccessPoint(Context context, ScanResult result) {
    160         mContext = context;
    161         initWithScanResult(result);
    162     }
    163 
    164     AccessPoint(Context context, WifiConfiguration config) {
    165         mContext = context;
    166         loadConfig(config);
    167     }
    168 
    169     @Override
    170     public int compareTo(AccessPoint other) {
    171         // Active one goes first.
    172         if (isActive() && !other.isActive()) return -1;
    173         if (!isActive() && other.isActive()) return 1;
    174 
    175         // Reachable one goes before unreachable one.
    176         if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
    177         if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
    178 
    179         // Configured one goes before unconfigured one.
    180         if (networkId != WifiConfiguration.INVALID_NETWORK_ID
    181                 && other.networkId == WifiConfiguration.INVALID_NETWORK_ID) return -1;
    182         if (networkId == WifiConfiguration.INVALID_NETWORK_ID
    183                 && other.networkId != WifiConfiguration.INVALID_NETWORK_ID) return 1;
    184 
    185         // Sort by signal strength.
    186         int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi);
    187         if (difference != 0) {
    188             return difference;
    189         }
    190         // Sort by ssid.
    191         return ssid.compareToIgnoreCase(other.ssid);
    192     }
    193 
    194     @Override
    195     public boolean equals(Object other) {
    196         if (!(other instanceof AccessPoint)) return false;
    197         return (this.compareTo((AccessPoint) other) == 0);
    198     }
    199 
    200     @Override
    201     public int hashCode() {
    202         int result = 0;
    203         if (mInfo != null) result += 13 * mInfo.hashCode();
    204         result += 19 * mRssi;
    205         result += 23 * networkId;
    206         result += 29 * ssid.hashCode();
    207         return result;
    208     }
    209 
    210     @Override
    211     public String toString() {
    212         StringBuilder builder = new StringBuilder().append("AccessPoint(")
    213                 .append(ssid);
    214         if (isSaved()) {
    215             builder.append(',').append("saved");
    216         }
    217         if (isActive()) {
    218             builder.append(',').append("active");
    219         }
    220         if (isEphemeral()) {
    221             builder.append(',').append("ephemeral");
    222         }
    223         if (isConnectable()) {
    224             builder.append(',').append("connectable");
    225         }
    226         if (security != SECURITY_NONE) {
    227             builder.append(',').append(securityToString(security, pskType));
    228         }
    229         return builder.append(')').toString();
    230     }
    231 
    232     public boolean matches(ScanResult result) {
    233         return ssid.equals(result.SSID) && security == getSecurity(result);
    234     }
    235 
    236     public boolean matches(WifiConfiguration config) {
    237         if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint())
    238             return config.FQDN.equals(mConfig.providerFriendlyName);
    239         else
    240             return ssid.equals(removeDoubleQuotes(config.SSID)) && security == getSecurity(config);
    241     }
    242 
    243     public WifiConfiguration getConfig() {
    244         return mConfig;
    245     }
    246 
    247     public void clearConfig() {
    248         mConfig = null;
    249         networkId = WifiConfiguration.INVALID_NETWORK_ID;
    250     }
    251 
    252     public WifiInfo getInfo() {
    253         return mInfo;
    254     }
    255 
    256     public int getLevel() {
    257         if (mRssi == Integer.MAX_VALUE) {
    258             return -1;
    259         }
    260         return WifiManager.calculateSignalLevel(mRssi, 4);
    261     }
    262 
    263     public int getRssi() {
    264         int rssi = Integer.MIN_VALUE;
    265         for (ScanResult result : mScanResultCache.snapshot().values()) {
    266             if (result.level > rssi) {
    267                 rssi = result.level;
    268             }
    269         }
    270 
    271         return rssi;
    272     }
    273 
    274     public long getSeen() {
    275         long seen = 0;
    276         for (ScanResult result : mScanResultCache.snapshot().values()) {
    277             if (result.timestamp > seen) {
    278                 seen = result.timestamp;
    279             }
    280         }
    281 
    282         return seen;
    283     }
    284 
    285     public NetworkInfo getNetworkInfo() {
    286         return mNetworkInfo;
    287     }
    288 
    289     public int getSecurity() {
    290         return security;
    291     }
    292 
    293     public String getSecurityString(boolean concise) {
    294         Context context = mContext;
    295         if (mConfig != null && mConfig.isPasspoint()) {
    296             return concise ? context.getString(R.string.wifi_security_short_eap) :
    297                 context.getString(R.string.wifi_security_eap);
    298         }
    299         switch(security) {
    300             case SECURITY_EAP:
    301                 return concise ? context.getString(R.string.wifi_security_short_eap) :
    302                     context.getString(R.string.wifi_security_eap);
    303             case SECURITY_PSK:
    304                 switch (pskType) {
    305                     case PSK_WPA:
    306                         return concise ? context.getString(R.string.wifi_security_short_wpa) :
    307                             context.getString(R.string.wifi_security_wpa);
    308                     case PSK_WPA2:
    309                         return concise ? context.getString(R.string.wifi_security_short_wpa2) :
    310                             context.getString(R.string.wifi_security_wpa2);
    311                     case PSK_WPA_WPA2:
    312                         return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
    313                             context.getString(R.string.wifi_security_wpa_wpa2);
    314                     case PSK_UNKNOWN:
    315                     default:
    316                         return concise ? context.getString(R.string.wifi_security_short_psk_generic)
    317                                 : context.getString(R.string.wifi_security_psk_generic);
    318                 }
    319             case SECURITY_WEP:
    320                 return concise ? context.getString(R.string.wifi_security_short_wep) :
    321                     context.getString(R.string.wifi_security_wep);
    322             case SECURITY_NONE:
    323             default:
    324                 return concise ? "" : context.getString(R.string.wifi_security_none);
    325         }
    326     }
    327 
    328     public String getSsidStr() {
    329         return ssid;
    330     }
    331 
    332     public CharSequence getSsid() {
    333         SpannableString str = new SpannableString(ssid);
    334         str.setSpan(new TtsSpan.VerbatimBuilder(ssid).build(), 0, ssid.length(),
    335                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    336         return str;
    337     }
    338 
    339     public String getConfigName() {
    340         if (mConfig != null && mConfig.isPasspoint()) {
    341             return mConfig.providerFriendlyName;
    342         } else {
    343             return ssid;
    344         }
    345     }
    346 
    347     public DetailedState getDetailedState() {
    348         return mNetworkInfo != null ? mNetworkInfo.getDetailedState() : null;
    349     }
    350 
    351     public String getSavedNetworkSummary() {
    352         if (mConfig != null) {
    353             PackageManager pm = mContext.getPackageManager();
    354             String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID);
    355             int userId = UserHandle.getUserId(mConfig.creatorUid);
    356             ApplicationInfo appInfo = null;
    357             if (mConfig.creatorName != null && mConfig.creatorName.equals(systemName)) {
    358                 appInfo = mContext.getApplicationInfo();
    359             } else {
    360                 try {
    361                     IPackageManager ipm = AppGlobals.getPackageManager();
    362                     appInfo = ipm.getApplicationInfo(mConfig.creatorName, 0 /* flags */, userId);
    363                 } catch (RemoteException rex) {
    364                 }
    365             }
    366             if (appInfo != null &&
    367                     !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) &&
    368                     !appInfo.packageName.equals(
    369                     mContext.getString(R.string.certinstaller_package))) {
    370                 return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm));
    371             }
    372         }
    373         return "";
    374     }
    375 
    376     public String getSummary() {
    377         return getSettingsSummary();
    378     }
    379 
    380     public String getSettingsSummary() {
    381         // Update to new summary
    382         StringBuilder summary = new StringBuilder();
    383 
    384         if (isActive() && mConfig != null && mConfig.isPasspoint()) {
    385             // This is the active connection on passpoint
    386             summary.append(getSummary(mContext, getDetailedState(),
    387                     false, mConfig.providerFriendlyName));
    388         } else if (isActive()) {
    389             // This is the active connection on non-passpoint network
    390             summary.append(getSummary(mContext, getDetailedState(),
    391                     mInfo != null && mInfo.isEphemeral()));
    392         } else if (mConfig != null && mConfig.isPasspoint()) {
    393             String format = mContext.getString(R.string.available_via_passpoint);
    394             summary.append(String.format(format, mConfig.providerFriendlyName));
    395         } else if (mConfig != null && mConfig.hasNoInternetAccess()) {
    396             summary.append(mContext.getString(R.string.wifi_no_internet));
    397         } else if (mConfig != null && ((mConfig.status == WifiConfiguration.Status.DISABLED &&
    398                 mConfig.disableReason != WifiConfiguration.DISABLED_UNKNOWN_REASON)
    399                || mConfig.autoJoinStatus
    400                 >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE)) {
    401             if (mConfig.autoJoinStatus
    402                     >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
    403                 if (mConfig.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE) {
    404                     summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
    405                 } else if (mConfig.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE) {
    406                     summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
    407                 } else {
    408                     summary.append(mContext.getString(R.string.wifi_disabled_wifi_failure));
    409                 }
    410             } else {
    411                 switch (mConfig.disableReason) {
    412                     case WifiConfiguration.DISABLED_AUTH_FAILURE:
    413                         summary.append(mContext.getString(R.string.wifi_disabled_password_failure));
    414                         break;
    415                     case WifiConfiguration.DISABLED_DHCP_FAILURE:
    416                     case WifiConfiguration.DISABLED_DNS_FAILURE:
    417                         summary.append(mContext.getString(R.string.wifi_disabled_network_failure));
    418                         break;
    419                     case WifiConfiguration.DISABLED_UNKNOWN_REASON:
    420                     case WifiConfiguration.DISABLED_ASSOCIATION_REJECT:
    421                         summary.append(mContext.getString(R.string.wifi_disabled_generic));
    422                         break;
    423                 }
    424             }
    425         } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
    426             summary.append(mContext.getString(R.string.wifi_not_in_range));
    427         } else { // In range, not disabled.
    428             if (mConfig != null) { // Is saved network
    429                 summary.append(mContext.getString(R.string.wifi_remembered));
    430             }
    431         }
    432 
    433         if (WifiTracker.sVerboseLogging > 0) {
    434             // Add RSSI/band information for this config, what was seen up to 6 seconds ago
    435             // verbose WiFi Logging is only turned on thru developers settings
    436             if (mInfo != null && mNetworkInfo != null) { // This is the active connection
    437                 summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
    438             }
    439             summary.append(" " + getVisibilityStatus());
    440             if (mConfig != null && mConfig.autoJoinStatus > 0) {
    441                 summary.append(" (" + mConfig.autoJoinStatus);
    442                 if (mConfig.blackListTimestamp > 0) {
    443                     long now = System.currentTimeMillis();
    444                     long diff = (now - mConfig.blackListTimestamp)/1000;
    445                     long sec = diff%60; //seconds
    446                     long min = (diff/60)%60; //minutes
    447                     long hour = (min/60)%60; //hours
    448                     summary.append(", ");
    449                     if (hour > 0) summary.append(Long.toString(hour) + "h ");
    450                     summary.append( Long.toString(min) + "m ");
    451                     summary.append( Long.toString(sec) + "s ");
    452                 }
    453                 summary.append(")");
    454             }
    455             if (mConfig != null && mConfig.numIpConfigFailures > 0) {
    456                 summary.append(" ipf=").append(mConfig.numIpConfigFailures);
    457             }
    458             if (mConfig != null && mConfig.numConnectionFailures > 0) {
    459                 summary.append(" cf=").append(mConfig.numConnectionFailures);
    460             }
    461             if (mConfig != null && mConfig.numAuthFailures > 0) {
    462                 summary.append(" authf=").append(mConfig.numAuthFailures);
    463             }
    464             if (mConfig != null && mConfig.numNoInternetAccessReports > 0) {
    465                 summary.append(" noInt=").append(mConfig.numNoInternetAccessReports);
    466             }
    467         }
    468         return summary.toString();
    469     }
    470 
    471     /**
    472      * Returns the visibility status of the WifiConfiguration.
    473      *
    474      * @return autojoin debugging information
    475      * TODO: use a string formatter
    476      * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
    477      * For instance [-40,5/-30,2]
    478      */
    479     private String getVisibilityStatus() {
    480         StringBuilder visibility = new StringBuilder();
    481         StringBuilder scans24GHz = null;
    482         StringBuilder scans5GHz = null;
    483         String bssid = null;
    484 
    485         long now = System.currentTimeMillis();
    486 
    487         if (mInfo != null) {
    488             bssid = mInfo.getBSSID();
    489             if (bssid != null) {
    490                 visibility.append(" ").append(bssid);
    491             }
    492             visibility.append(" rssi=").append(mInfo.getRssi());
    493             visibility.append(" ");
    494             visibility.append(" score=").append(mInfo.score);
    495             visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
    496             visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
    497             visibility.append(String.format("%.1f ", mInfo.txBadRate));
    498             visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
    499         }
    500 
    501         int rssi5 = WifiConfiguration.INVALID_RSSI;
    502         int rssi24 = WifiConfiguration.INVALID_RSSI;
    503         int num5 = 0;
    504         int num24 = 0;
    505         int numBlackListed = 0;
    506         int n24 = 0; // Number scan results we included in the string
    507         int n5 = 0; // Number scan results we included in the string
    508         Map<String, ScanResult> list = mScanResultCache.snapshot();
    509         // TODO: sort list by RSSI or age
    510         for (ScanResult result : list.values()) {
    511             if (result.seen == 0)
    512                 continue;
    513 
    514             if (result.autoJoinStatus != ScanResult.ENABLED) numBlackListed++;
    515 
    516             if (result.frequency >= LOWER_FREQ_5GHZ
    517                     && result.frequency <= HIGHER_FREQ_5GHZ) {
    518                 // Strictly speaking: [4915, 5825]
    519                 // number of known BSSID on 5GHz band
    520                 num5 = num5 + 1;
    521             } else if (result.frequency >= LOWER_FREQ_24GHZ
    522                     && result.frequency <= HIGHER_FREQ_24GHZ) {
    523                 // Strictly speaking: [2412, 2482]
    524                 // number of known BSSID on 2.4Ghz band
    525                 num24 = num24 + 1;
    526             }
    527 
    528             // Ignore results seen, older than 20 seconds
    529             if (now - result.seen > VISIBILITY_OUTDATED_AGE_IN_MILLI) continue;
    530 
    531             if (result.frequency >= LOWER_FREQ_5GHZ
    532                     && result.frequency <= HIGHER_FREQ_5GHZ) {
    533                 if (result.level > rssi5) {
    534                     rssi5 = result.level;
    535                 }
    536                 if (n5 < 4) {
    537                     if (scans5GHz == null) scans5GHz = new StringBuilder();
    538                     scans5GHz.append(" \n{").append(result.BSSID);
    539                     if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*");
    540                     scans5GHz.append("=").append(result.frequency);
    541                     scans5GHz.append(",").append(result.level);
    542                     if (result.autoJoinStatus != 0) {
    543                         scans5GHz.append(",st=").append(result.autoJoinStatus);
    544                     }
    545                     if (result.numIpConfigFailures != 0) {
    546                         scans5GHz.append(",ipf=").append(result.numIpConfigFailures);
    547                     }
    548                     scans5GHz.append("}");
    549                     n5++;
    550                 }
    551             } else if (result.frequency >= LOWER_FREQ_24GHZ
    552                     && result.frequency <= HIGHER_FREQ_24GHZ) {
    553                 if (result.level > rssi24) {
    554                     rssi24 = result.level;
    555                 }
    556                 if (n24 < 4) {
    557                     if (scans24GHz == null) scans24GHz = new StringBuilder();
    558                     scans24GHz.append(" \n{").append(result.BSSID);
    559                     if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*");
    560                     scans24GHz.append("=").append(result.frequency);
    561                     scans24GHz.append(",").append(result.level);
    562                     if (result.autoJoinStatus != 0) {
    563                         scans24GHz.append(",st=").append(result.autoJoinStatus);
    564                     }
    565                     if (result.numIpConfigFailures != 0) {
    566                         scans24GHz.append(",ipf=").append(result.numIpConfigFailures);
    567                     }
    568                     scans24GHz.append("}");
    569                     n24++;
    570                 }
    571             }
    572         }
    573         visibility.append(" [");
    574         if (num24 > 0) {
    575             visibility.append("(").append(num24).append(")");
    576             if (n24 <= 4) {
    577                 if (scans24GHz != null) {
    578                     visibility.append(scans24GHz.toString());
    579                 }
    580             } else {
    581                 visibility.append("max=").append(rssi24);
    582                 if (scans24GHz != null) {
    583                     visibility.append(",").append(scans24GHz.toString());
    584                 }
    585             }
    586         }
    587         visibility.append(";");
    588         if (num5 > 0) {
    589             visibility.append("(").append(num5).append(")");
    590             if (n5 <= 4) {
    591                 if (scans5GHz != null) {
    592                     visibility.append(scans5GHz.toString());
    593                 }
    594             } else {
    595                 visibility.append("max=").append(rssi5);
    596                 if (scans5GHz != null) {
    597                     visibility.append(",").append(scans5GHz.toString());
    598                 }
    599             }
    600         }
    601         if (numBlackListed > 0)
    602             visibility.append("!").append(numBlackListed);
    603         visibility.append("]");
    604 
    605         return visibility.toString();
    606     }
    607 
    608     /**
    609      * Return whether this is the active connection.
    610      * For ephemeral connections (networkId is invalid), this returns false if the network is
    611      * disconnected.
    612      */
    613     public boolean isActive() {
    614         return mNetworkInfo != null &&
    615                 (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
    616                  mNetworkInfo.getState() != State.DISCONNECTED);
    617     }
    618 
    619     public boolean isConnectable() {
    620         return getLevel() != -1 && getDetailedState() == null;
    621     }
    622 
    623     public boolean isEphemeral() {
    624         return mInfo != null && mInfo.isEphemeral() &&
    625                 mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED;
    626     }
    627 
    628     public boolean isPasspoint() {
    629         return mConfig != null && mConfig.isPasspoint();
    630     }
    631 
    632     /**
    633      * Return whether the given {@link WifiInfo} is for this access point.
    634      * If the current AP does not have a network Id then the config is used to
    635      * match based on SSID and security.
    636      */
    637     private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
    638         if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
    639             return networkId == info.getNetworkId();
    640         } else if (config != null) {
    641             return matches(config);
    642         }
    643         else {
    644             // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
    645             // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
    646             // TODO: Handle hex string SSIDs.
    647             return ssid.equals(removeDoubleQuotes(info.getSSID()));
    648         }
    649     }
    650 
    651     public boolean isSaved() {
    652         return networkId != WifiConfiguration.INVALID_NETWORK_ID;
    653     }
    654 
    655     public Object getTag() {
    656         return mTag;
    657     }
    658 
    659     public void setTag(Object tag) {
    660         mTag = tag;
    661     }
    662 
    663     /**
    664      * Generate and save a default wifiConfiguration with common values.
    665      * Can only be called for unsecured networks.
    666      */
    667     public void generateOpenNetworkConfig() {
    668         if (security != SECURITY_NONE)
    669             throw new IllegalStateException();
    670         if (mConfig != null)
    671             return;
    672         mConfig = new WifiConfiguration();
    673         mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
    674         mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
    675     }
    676 
    677     void loadConfig(WifiConfiguration config) {
    678         if (config.isPasspoint())
    679             ssid = config.providerFriendlyName;
    680         else
    681             ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
    682 
    683         security = getSecurity(config);
    684         networkId = config.networkId;
    685         mConfig = config;
    686     }
    687 
    688     private void initWithScanResult(ScanResult result) {
    689         ssid = result.SSID;
    690         security = getSecurity(result);
    691         if (security == SECURITY_PSK)
    692             pskType = getPskType(result);
    693         mRssi = result.level;
    694         mSeen = result.timestamp;
    695     }
    696 
    697     public void saveWifiState(Bundle savedState) {
    698         if (ssid != null) savedState.putString(KEY_SSID, getSsidStr());
    699         savedState.putInt(KEY_SECURITY, security);
    700         savedState.putInt(KEY_PSKTYPE, pskType);
    701         if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
    702         savedState.putParcelable(KEY_WIFIINFO, mInfo);
    703         savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
    704                 new ArrayList<ScanResult>(mScanResultCache.snapshot().values()));
    705         if (mNetworkInfo != null) {
    706             savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
    707         }
    708     }
    709 
    710     public void setListener(AccessPointListener listener) {
    711         mAccessPointListener = listener;
    712     }
    713 
    714     boolean update(ScanResult result) {
    715         if (matches(result)) {
    716             /* Update the LRU timestamp, if BSSID exists */
    717             mScanResultCache.get(result.BSSID);
    718 
    719             /* Add or update the scan result for the BSSID */
    720             mScanResultCache.put(result.BSSID, result);
    721 
    722             int oldLevel = getLevel();
    723             int oldRssi = getRssi();
    724             mSeen = getSeen();
    725             mRssi = (getRssi() + oldRssi)/2;
    726             int newLevel = getLevel();
    727 
    728             if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) {
    729                 mAccessPointListener.onLevelChanged(this);
    730             }
    731             // This flag only comes from scans, is not easily saved in config
    732             if (security == SECURITY_PSK) {
    733                 pskType = getPskType(result);
    734             }
    735 
    736             if (mAccessPointListener != null) {
    737                 mAccessPointListener.onAccessPointChanged(this);
    738             }
    739 
    740             return true;
    741         }
    742         return false;
    743     }
    744 
    745     boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
    746         boolean reorder = false;
    747         if (info != null && isInfoForThisAccessPoint(config, info)) {
    748             reorder = (mInfo == null);
    749             mRssi = info.getRssi();
    750             mInfo = info;
    751             mNetworkInfo = networkInfo;
    752             if (mAccessPointListener != null) {
    753                 mAccessPointListener.onAccessPointChanged(this);
    754             }
    755         } else if (mInfo != null) {
    756             reorder = true;
    757             mInfo = null;
    758             mNetworkInfo = null;
    759             if (mAccessPointListener != null) {
    760                 mAccessPointListener.onAccessPointChanged(this);
    761             }
    762         }
    763         return reorder;
    764     }
    765 
    766     void update(WifiConfiguration config) {
    767         mConfig = config;
    768         networkId = config.networkId;
    769         if (mAccessPointListener != null) {
    770             mAccessPointListener.onAccessPointChanged(this);
    771         }
    772     }
    773 
    774     public static String getSummary(Context context, String ssid, DetailedState state,
    775             boolean isEphemeral, String passpointProvider) {
    776         if (state == DetailedState.CONNECTED && ssid == null) {
    777             if (TextUtils.isEmpty(passpointProvider) == false) {
    778                 // Special case for connected + passpoint networks.
    779                 String format = context.getString(R.string.connected_via_passpoint);
    780                 return String.format(format, passpointProvider);
    781             } else if (isEphemeral) {
    782                 // Special case for connected + ephemeral networks.
    783                 return context.getString(R.string.connected_via_wfa);
    784             }
    785         }
    786 
    787         // Case when there is wifi connected without internet connectivity.
    788         final ConnectivityManager cm = (ConnectivityManager)
    789                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
    790         if (state == DetailedState.CONNECTED) {
    791             IWifiManager wifiManager = IWifiManager.Stub.asInterface(
    792                     ServiceManager.getService(Context.WIFI_SERVICE));
    793             Network nw;
    794 
    795             try {
    796                 nw = wifiManager.getCurrentNetwork();
    797             } catch (RemoteException e) {
    798                 nw = null;
    799             }
    800             NetworkCapabilities nc = cm.getNetworkCapabilities(nw);
    801             if (nc != null && !nc.hasCapability(nc.NET_CAPABILITY_VALIDATED)) {
    802                 return context.getString(R.string.wifi_connected_no_internet);
    803             }
    804         }
    805 
    806         String[] formats = context.getResources().getStringArray((ssid == null)
    807                 ? R.array.wifi_status : R.array.wifi_status_with_ssid);
    808         int index = state.ordinal();
    809 
    810         if (index >= formats.length || formats[index].length() == 0) {
    811             return "";
    812         }
    813         return String.format(formats[index], ssid);
    814     }
    815 
    816     public static String getSummary(Context context, DetailedState state, boolean isEphemeral) {
    817         return getSummary(context, null, state, isEphemeral, null);
    818     }
    819 
    820     public static String getSummary(Context context, DetailedState state, boolean isEphemeral,
    821             String passpointProvider) {
    822         return getSummary(context, null, state, isEphemeral, passpointProvider);
    823     }
    824 
    825     public static String convertToQuotedString(String string) {
    826         return "\"" + string + "\"";
    827     }
    828 
    829     private static int getPskType(ScanResult result) {
    830         boolean wpa = result.capabilities.contains("WPA-PSK");
    831         boolean wpa2 = result.capabilities.contains("WPA2-PSK");
    832         if (wpa2 && wpa) {
    833             return PSK_WPA_WPA2;
    834         } else if (wpa2) {
    835             return PSK_WPA2;
    836         } else if (wpa) {
    837             return PSK_WPA;
    838         } else {
    839             Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
    840             return PSK_UNKNOWN;
    841         }
    842     }
    843 
    844     private static int getSecurity(ScanResult result) {
    845         if (result.capabilities.contains("WEP")) {
    846             return SECURITY_WEP;
    847         } else if (result.capabilities.contains("PSK")) {
    848             return SECURITY_PSK;
    849         } else if (result.capabilities.contains("EAP")) {
    850             return SECURITY_EAP;
    851         }
    852         return SECURITY_NONE;
    853     }
    854 
    855     static int getSecurity(WifiConfiguration config) {
    856         if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
    857             return SECURITY_PSK;
    858         }
    859         if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
    860                 config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
    861             return SECURITY_EAP;
    862         }
    863         return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
    864     }
    865 
    866     public static String securityToString(int security, int pskType) {
    867         if (security == SECURITY_WEP) {
    868             return "WEP";
    869         } else if (security == SECURITY_PSK) {
    870             if (pskType == PSK_WPA) {
    871                 return "WPA";
    872             } else if (pskType == PSK_WPA2) {
    873                 return "WPA2";
    874             } else if (pskType == PSK_WPA_WPA2) {
    875                 return "WPA_WPA2";
    876             }
    877             return "PSK";
    878         } else if (security == SECURITY_EAP) {
    879             return "EAP";
    880         }
    881         return "NONE";
    882     }
    883 
    884     static String removeDoubleQuotes(String string) {
    885         if (TextUtils.isEmpty(string)) {
    886             return "";
    887         }
    888         int length = string.length();
    889         if ((length > 1) && (string.charAt(0) == '"')
    890                 && (string.charAt(length - 1) == '"')) {
    891             return string.substring(1, length - 1);
    892         }
    893         return string;
    894     }
    895 
    896     public interface AccessPointListener {
    897         void onAccessPointChanged(AccessPoint accessPoint);
    898         void onLevelChanged(AccessPoint accessPoint);
    899     }
    900 }
    901