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 package com.android.settingslib.wifi;
     17 
     18 import android.content.BroadcastReceiver;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.IntentFilter;
     22 import android.net.ConnectivityManager;
     23 import android.net.Network;
     24 import android.net.NetworkCapabilities;
     25 import android.net.NetworkInfo;
     26 import android.net.NetworkInfo.DetailedState;
     27 import android.net.NetworkRequest;
     28 import android.net.wifi.ScanResult;
     29 import android.net.wifi.WifiConfiguration;
     30 import android.net.wifi.WifiInfo;
     31 import android.net.wifi.WifiManager;
     32 import android.os.Handler;
     33 import android.os.Looper;
     34 import android.os.Message;
     35 import android.util.Log;
     36 import android.widget.Toast;
     37 
     38 import com.android.internal.annotations.VisibleForTesting;
     39 import com.android.settingslib.R;
     40 
     41 import java.io.PrintWriter;
     42 import java.util.ArrayList;
     43 import java.util.Collection;
     44 import java.util.Collections;
     45 import java.util.HashMap;
     46 import java.util.Iterator;
     47 import java.util.List;
     48 import java.util.Map;
     49 import java.util.concurrent.atomic.AtomicBoolean;
     50 
     51 /**
     52  * Tracks saved or available wifi networks and their state.
     53  */
     54 public class WifiTracker {
     55     private static final String TAG = "WifiTracker";
     56     private static final boolean DBG = false;
     57 
     58     /** verbose logging flag. this flag is set thru developer debugging options
     59      * and used so as to assist with in-the-field WiFi connectivity debugging  */
     60     public static int sVerboseLogging = 0;
     61 
     62     // TODO: Allow control of this?
     63     // Combo scans can take 5-6s to complete - set to 10s.
     64     private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
     65 
     66     private final Context mContext;
     67     private final WifiManager mWifiManager;
     68     private final IntentFilter mFilter;
     69     private final ConnectivityManager mConnectivityManager;
     70     private final NetworkRequest mNetworkRequest;
     71     private WifiTrackerNetworkCallback mNetworkCallback;
     72 
     73     private final AtomicBoolean mConnected = new AtomicBoolean(false);
     74     private final WifiListener mListener;
     75     private final boolean mIncludeSaved;
     76     private final boolean mIncludeScans;
     77     private final boolean mIncludePasspoints;
     78 
     79     private final MainHandler mMainHandler;
     80     private final WorkHandler mWorkHandler;
     81 
     82     private boolean mSavedNetworksExist;
     83     private boolean mRegistered;
     84     private ArrayList<AccessPoint> mAccessPoints = new ArrayList<>();
     85     private HashMap<String, Integer> mSeenBssids = new HashMap<>();
     86     private HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
     87     private Integer mScanId = 0;
     88     private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
     89 
     90     private NetworkInfo mLastNetworkInfo;
     91     private WifiInfo mLastInfo;
     92 
     93     @VisibleForTesting
     94     Scanner mScanner;
     95 
     96     public WifiTracker(Context context, WifiListener wifiListener,
     97             boolean includeSaved, boolean includeScans) {
     98         this(context, wifiListener, null, includeSaved, includeScans);
     99     }
    100 
    101     public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
    102             boolean includeSaved, boolean includeScans) {
    103         this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
    104     }
    105 
    106     public WifiTracker(Context context, WifiListener wifiListener,
    107             boolean includeSaved, boolean includeScans, boolean includePasspoints) {
    108         this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
    109     }
    110 
    111     public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
    112             boolean includeSaved, boolean includeScans, boolean includePasspoints) {
    113         this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
    114                 context.getSystemService(WifiManager.class),
    115                 context.getSystemService(ConnectivityManager.class), Looper.myLooper());
    116     }
    117 
    118     @VisibleForTesting
    119     WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
    120             boolean includeSaved, boolean includeScans, boolean includePasspoints,
    121             WifiManager wifiManager, ConnectivityManager connectivityManager,
    122             Looper currentLooper) {
    123         if (!includeSaved && !includeScans) {
    124             throw new IllegalArgumentException("Must include either saved or scans");
    125         }
    126         mContext = context;
    127         if (currentLooper == null) {
    128             // When we aren't on a looper thread, default to the main.
    129             currentLooper = Looper.getMainLooper();
    130         }
    131         mMainHandler = new MainHandler(currentLooper);
    132         mWorkHandler = new WorkHandler(
    133                 workerLooper != null ? workerLooper : currentLooper);
    134         mWifiManager = wifiManager;
    135         mIncludeSaved = includeSaved;
    136         mIncludeScans = includeScans;
    137         mIncludePasspoints = includePasspoints;
    138         mListener = wifiListener;
    139         mConnectivityManager = connectivityManager;
    140 
    141         // check if verbose logging has been turned on or off
    142         sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
    143 
    144         mFilter = new IntentFilter();
    145         mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    146         mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    147         mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
    148         mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    149         mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
    150         mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
    151         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    152 
    153         mNetworkRequest = new NetworkRequest.Builder()
    154                 .clearCapabilities()
    155                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    156                 .build();
    157     }
    158 
    159     /**
    160      * Forces an update of the wifi networks when not scanning.
    161      */
    162     public void forceUpdate() {
    163         updateAccessPoints();
    164     }
    165 
    166     /**
    167      * Force a scan for wifi networks to happen now.
    168      */
    169     public void forceScan() {
    170         if (mWifiManager.isWifiEnabled() && mScanner != null) {
    171             mScanner.forceScan();
    172         }
    173     }
    174 
    175     /**
    176      * Temporarily stop scanning for wifi networks.
    177      */
    178     public void pauseScanning() {
    179         if (mScanner != null) {
    180             mScanner.pause();
    181             mScanner = null;
    182         }
    183     }
    184 
    185     /**
    186      * Resume scanning for wifi networks after it has been paused.
    187      */
    188     public void resumeScanning() {
    189         if (mScanner == null) {
    190             mScanner = new Scanner();
    191         }
    192 
    193         mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
    194         if (mWifiManager.isWifiEnabled()) {
    195             mScanner.resume();
    196         }
    197         mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
    198     }
    199 
    200     /**
    201      * Start tracking wifi networks.
    202      * Registers listeners and starts scanning for wifi networks. If this is not called
    203      * then forceUpdate() must be called to populate getAccessPoints().
    204      */
    205     public void startTracking() {
    206         resumeScanning();
    207         if (!mRegistered) {
    208             mContext.registerReceiver(mReceiver, mFilter);
    209             // NetworkCallback objects cannot be reused. http://b/20701525 .
    210             mNetworkCallback = new WifiTrackerNetworkCallback();
    211             mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
    212             mRegistered = true;
    213         }
    214     }
    215 
    216     /**
    217      * Stop tracking wifi networks.
    218      * Unregisters all listeners and stops scanning for wifi networks. This should always
    219      * be called when done with a WifiTracker (if startTracking was called) to ensure
    220      * proper cleanup.
    221      */
    222     public void stopTracking() {
    223         if (mRegistered) {
    224             mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
    225             mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_INFO);
    226             mContext.unregisterReceiver(mReceiver);
    227             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
    228             mRegistered = false;
    229         }
    230         pauseScanning();
    231     }
    232 
    233     /**
    234      * Gets the current list of access points.
    235      */
    236     public List<AccessPoint> getAccessPoints() {
    237         synchronized (mAccessPoints) {
    238             return new ArrayList<>(mAccessPoints);
    239         }
    240     }
    241 
    242     public WifiManager getManager() {
    243         return mWifiManager;
    244     }
    245 
    246     public boolean isWifiEnabled() {
    247         return mWifiManager.isWifiEnabled();
    248     }
    249 
    250     /**
    251      * @return true when there are saved networks on the device, regardless
    252      * of whether the WifiTracker is tracking saved networks.
    253      */
    254     public boolean doSavedNetworksExist() {
    255         return mSavedNetworksExist;
    256     }
    257 
    258     public boolean isConnected() {
    259         return mConnected.get();
    260     }
    261 
    262     public void dump(PrintWriter pw) {
    263         pw.println("  - wifi tracker ------");
    264         for (AccessPoint accessPoint : getAccessPoints()) {
    265             pw.println("  " + accessPoint);
    266         }
    267     }
    268 
    269     private void handleResume() {
    270         mScanResultCache.clear();
    271         mSeenBssids.clear();
    272         mScanId = 0;
    273     }
    274 
    275     private Collection<ScanResult> fetchScanResults() {
    276         mScanId++;
    277         final List<ScanResult> newResults = mWifiManager.getScanResults();
    278         for (ScanResult newResult : newResults) {
    279             if (newResult.SSID == null || newResult.SSID.isEmpty()) {
    280                 continue;
    281             }
    282             mScanResultCache.put(newResult.BSSID, newResult);
    283             mSeenBssids.put(newResult.BSSID, mScanId);
    284         }
    285 
    286         if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
    287             if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
    288             Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
    289             for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
    290                     it.hasNext(); /* nothing */) {
    291                 Map.Entry<String, Integer> e = it.next();
    292                 if (e.getValue() < threshold) {
    293                     ScanResult result = mScanResultCache.get(e.getKey());
    294                     if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
    295                     mScanResultCache.remove(e.getKey());
    296                     it.remove();
    297                 }
    298             }
    299             if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
    300         }
    301 
    302         return mScanResultCache.values();
    303     }
    304 
    305     private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) {
    306         final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
    307         if (configs != null) {
    308             for (WifiConfiguration config : configs) {
    309                 if (mLastInfo != null && networkId == config.networkId &&
    310                         !(config.selfAdded && config.numAssociation == 0)) {
    311                     return config;
    312                 }
    313             }
    314         }
    315         return null;
    316     }
    317 
    318     private void updateAccessPoints() {
    319         // Swap the current access points into a cached list.
    320         List<AccessPoint> cachedAccessPoints = getAccessPoints();
    321         ArrayList<AccessPoint> accessPoints = new ArrayList<>();
    322 
    323         // Clear out the configs so we don't think something is saved when it isn't.
    324         for (AccessPoint accessPoint : cachedAccessPoints) {
    325             accessPoint.clearConfig();
    326         }
    327 
    328         /** Lookup table to more quickly update AccessPoints by only considering objects with the
    329          * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
    330         Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
    331         WifiConfiguration connectionConfig = null;
    332         if (mLastInfo != null) {
    333             connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
    334         }
    335 
    336         final Collection<ScanResult> results = fetchScanResults();
    337 
    338         final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
    339         if (configs != null) {
    340             mSavedNetworksExist = configs.size() != 0;
    341             for (WifiConfiguration config : configs) {
    342                 if (config.selfAdded && config.numAssociation == 0) {
    343                     continue;
    344                 }
    345                 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
    346                 if (mLastInfo != null && mLastNetworkInfo != null) {
    347                     if (config.isPasspoint() == false) {
    348                         accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
    349                     }
    350                 }
    351                 if (mIncludeSaved) {
    352                     if (!config.isPasspoint() || mIncludePasspoints) {
    353                         // If saved network not present in scan result then set its Rssi to MAX_VALUE
    354                         boolean apFound = false;
    355                         for (ScanResult result : results) {
    356                             if (result.SSID.equals(accessPoint.getSsidStr())) {
    357                                 apFound = true;
    358                                 break;
    359                             }
    360                         }
    361                         if (!apFound) {
    362                             accessPoint.setRssi(Integer.MAX_VALUE);
    363                         }
    364                         accessPoints.add(accessPoint);
    365                     }
    366 
    367                     if (config.isPasspoint() == false) {
    368                         apMap.put(accessPoint.getSsidStr(), accessPoint);
    369                     }
    370                 } else {
    371                     // If we aren't using saved networks, drop them into the cache so that
    372                     // we have access to their saved info.
    373                     cachedAccessPoints.add(accessPoint);
    374                 }
    375             }
    376         }
    377 
    378         if (results != null) {
    379             for (ScanResult result : results) {
    380                 // Ignore hidden and ad-hoc networks.
    381                 if (result.SSID == null || result.SSID.length() == 0 ||
    382                         result.capabilities.contains("[IBSS]")) {
    383                     continue;
    384                 }
    385 
    386                 boolean found = false;
    387                 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
    388                     if (accessPoint.update(result)) {
    389                         found = true;
    390                         break;
    391                     }
    392                 }
    393                 if (!found && mIncludeScans) {
    394                     AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
    395                     if (mLastInfo != null && mLastNetworkInfo != null) {
    396                         accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
    397                     }
    398 
    399                     if (result.isPasspointNetwork()) {
    400                         WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
    401                         if (config != null) {
    402                             accessPoint.update(config);
    403                         }
    404                     }
    405 
    406                     if (mLastInfo != null && mLastInfo.getBSSID() != null
    407                             && mLastInfo.getBSSID().equals(result.BSSID)
    408                             && connectionConfig != null && connectionConfig.isPasspoint()) {
    409                         /* This network is connected via this passpoint config */
    410                         /* SSID match is not going to work for it; so update explicitly */
    411                         accessPoint.update(connectionConfig);
    412                     }
    413 
    414                     accessPoints.add(accessPoint);
    415                     apMap.put(accessPoint.getSsidStr(), accessPoint);
    416                 }
    417             }
    418         }
    419 
    420         // Pre-sort accessPoints to speed preference insertion
    421         Collections.sort(accessPoints);
    422 
    423         // Log accesspoints that were deleted
    424         if (DBG) Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
    425         for (AccessPoint prevAccessPoint : mAccessPoints) {
    426             if (prevAccessPoint.getSsid() == null) continue;
    427             String prevSsid = prevAccessPoint.getSsidStr();
    428             boolean found = false;
    429             for (AccessPoint newAccessPoint : accessPoints) {
    430                 if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid().equals(prevSsid)) {
    431                     found = true;
    432                     break;
    433                 }
    434             }
    435             if (!found)
    436                 if (DBG) Log.d(TAG, "Did not find " + prevSsid + " in this scan");
    437         }
    438         if (DBG)  Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
    439 
    440         mAccessPoints = accessPoints;
    441         mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
    442     }
    443 
    444     private AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
    445         final int N = cache.size();
    446         for (int i = 0; i < N; i++) {
    447             if (cache.get(i).matches(result)) {
    448                 AccessPoint ret = cache.remove(i);
    449                 ret.update(result);
    450                 return ret;
    451             }
    452         }
    453         return new AccessPoint(mContext, result);
    454     }
    455 
    456     private AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
    457         final int N = cache.size();
    458         for (int i = 0; i < N; i++) {
    459             if (cache.get(i).matches(config)) {
    460                 AccessPoint ret = cache.remove(i);
    461                 ret.loadConfig(config);
    462                 return ret;
    463             }
    464         }
    465         return new AccessPoint(mContext, config);
    466     }
    467 
    468     private void updateNetworkInfo(NetworkInfo networkInfo) {
    469         /* sticky broadcasts can call this when wifi is disabled */
    470         if (!mWifiManager.isWifiEnabled()) {
    471             mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
    472             return;
    473         }
    474 
    475         if (networkInfo != null &&
    476                 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
    477             mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
    478         } else {
    479             mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
    480         }
    481 
    482         if (networkInfo != null) {
    483             mLastNetworkInfo = networkInfo;
    484         }
    485 
    486         WifiConfiguration connectionConfig = null;
    487         mLastInfo = mWifiManager.getConnectionInfo();
    488         if (mLastInfo != null) {
    489             connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
    490         }
    491 
    492         boolean reorder = false;
    493         for (int i = mAccessPoints.size() - 1; i >= 0; --i) {
    494             if (mAccessPoints.get(i).update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
    495                 reorder = true;
    496             }
    497         }
    498         if (reorder) {
    499             synchronized (mAccessPoints) {
    500                 Collections.sort(mAccessPoints);
    501             }
    502             mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
    503         }
    504     }
    505 
    506     private void updateWifiState(int state) {
    507         mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
    508     }
    509 
    510     public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
    511             boolean includeScans, boolean includePasspoints) {
    512         WifiTracker tracker = new WifiTracker(context,
    513                 null, null, includeSaved, includeScans, includePasspoints);
    514         tracker.forceUpdate();
    515         return tracker.getAccessPoints();
    516     }
    517 
    518     @VisibleForTesting
    519     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    520         @Override
    521         public void onReceive(Context context, Intent intent) {
    522             String action = intent.getAction();
    523             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
    524                 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
    525                         WifiManager.WIFI_STATE_UNKNOWN));
    526             } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
    527                     WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
    528                     WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
    529                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
    530             } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
    531                 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
    532                         WifiManager.EXTRA_NETWORK_INFO);
    533                 mConnected.set(info.isConnected());
    534 
    535                 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
    536 
    537                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
    538                 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
    539                         .sendToTarget();
    540             }
    541         }
    542     };
    543 
    544     private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
    545         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
    546             if (network.equals(mWifiManager.getCurrentNetwork())) {
    547                 // We don't send a NetworkInfo object along with this message, because even if we
    548                 // fetch one from ConnectivityManager, it might be older than the most recent
    549                 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
    550                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
    551             }
    552         }
    553     }
    554 
    555     private final class MainHandler extends Handler {
    556         private static final int MSG_CONNECTED_CHANGED = 0;
    557         private static final int MSG_WIFI_STATE_CHANGED = 1;
    558         private static final int MSG_ACCESS_POINT_CHANGED = 2;
    559         private static final int MSG_RESUME_SCANNING = 3;
    560         private static final int MSG_PAUSE_SCANNING = 4;
    561 
    562         public MainHandler(Looper looper) {
    563             super(looper);
    564         }
    565 
    566         @Override
    567         public void handleMessage(Message msg) {
    568             if (mListener == null) {
    569                 return;
    570             }
    571             switch (msg.what) {
    572                 case MSG_CONNECTED_CHANGED:
    573                     mListener.onConnectedChanged();
    574                     break;
    575                 case MSG_WIFI_STATE_CHANGED:
    576                     mListener.onWifiStateChanged(msg.arg1);
    577                     break;
    578                 case MSG_ACCESS_POINT_CHANGED:
    579                     mListener.onAccessPointsChanged();
    580                     break;
    581                 case MSG_RESUME_SCANNING:
    582                     if (mScanner != null) {
    583                         mScanner.resume();
    584                     }
    585                     break;
    586                 case MSG_PAUSE_SCANNING:
    587                     if (mScanner != null) {
    588                         mScanner.pause();
    589                     }
    590                     break;
    591             }
    592         }
    593     }
    594 
    595     private final class WorkHandler extends Handler {
    596         private static final int MSG_UPDATE_ACCESS_POINTS = 0;
    597         private static final int MSG_UPDATE_NETWORK_INFO = 1;
    598         private static final int MSG_RESUME = 2;
    599         private static final int MSG_UPDATE_WIFI_STATE = 3;
    600 
    601         public WorkHandler(Looper looper) {
    602             super(looper);
    603         }
    604 
    605         @Override
    606         public void handleMessage(Message msg) {
    607             switch (msg.what) {
    608                 case MSG_UPDATE_ACCESS_POINTS:
    609                     updateAccessPoints();
    610                     break;
    611                 case MSG_UPDATE_NETWORK_INFO:
    612                     updateNetworkInfo((NetworkInfo) msg.obj);
    613                     break;
    614                 case MSG_RESUME:
    615                     handleResume();
    616                     break;
    617                 case MSG_UPDATE_WIFI_STATE:
    618                     if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
    619                         if (mScanner != null) {
    620                             // We only need to resume if mScanner isn't null because
    621                             // that means we want to be scanning.
    622                             mScanner.resume();
    623                         }
    624                     } else {
    625                         mLastInfo = null;
    626                         mLastNetworkInfo = null;
    627                         if (mScanner != null) {
    628                             mScanner.pause();
    629                         }
    630                     }
    631                     mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
    632                             .sendToTarget();
    633                     break;
    634             }
    635         }
    636     }
    637 
    638     @VisibleForTesting
    639     class Scanner extends Handler {
    640         static final int MSG_SCAN = 0;
    641 
    642         private int mRetry = 0;
    643 
    644         void resume() {
    645             if (!hasMessages(MSG_SCAN)) {
    646                 sendEmptyMessage(MSG_SCAN);
    647             }
    648         }
    649 
    650         void forceScan() {
    651             removeMessages(MSG_SCAN);
    652             sendEmptyMessage(MSG_SCAN);
    653         }
    654 
    655         void pause() {
    656             mRetry = 0;
    657             removeMessages(MSG_SCAN);
    658         }
    659 
    660         @VisibleForTesting
    661         boolean isScanning() {
    662             return hasMessages(MSG_SCAN);
    663         }
    664 
    665         @Override
    666         public void handleMessage(Message message) {
    667             if (message.what != MSG_SCAN) return;
    668             if (mWifiManager.startScan()) {
    669                 mRetry = 0;
    670             } else if (++mRetry >= 3) {
    671                 mRetry = 0;
    672                 if (mContext != null) {
    673                     Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
    674                 }
    675                 return;
    676             }
    677             sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
    678         }
    679     }
    680 
    681     /** A restricted multimap for use in constructAccessPoints */
    682     private static class Multimap<K,V> {
    683         private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
    684         /** retrieve a non-null list of values with key K */
    685         List<V> getAll(K key) {
    686             List<V> values = store.get(key);
    687             return values != null ? values : Collections.<V>emptyList();
    688         }
    689 
    690         void put(K key, V val) {
    691             List<V> curVals = store.get(key);
    692             if (curVals == null) {
    693                 curVals = new ArrayList<V>(3);
    694                 store.put(key, curVals);
    695             }
    696             curVals.add(val);
    697         }
    698     }
    699 
    700     public interface WifiListener {
    701         /**
    702          * Called when the state of Wifi has changed, the state will be one of
    703          * the following.
    704          *
    705          * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
    706          * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
    707          * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
    708          * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
    709          * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
    710          * <p>
    711          *
    712          * @param state The new state of wifi.
    713          */
    714         void onWifiStateChanged(int state);
    715 
    716         /**
    717          * Called when the connection state of wifi has changed and isConnected
    718          * should be called to get the updated state.
    719          */
    720         void onConnectedChanged();
    721 
    722         /**
    723          * Called to indicate the list of AccessPoints has been updated and
    724          * getAccessPoints should be called to get the latest information.
    725          */
    726         void onAccessPointsChanged();
    727     }
    728 }
    729