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.annotation.MainThread;
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.database.ContentObserver;
     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.NetworkKey;
     30 import android.net.NetworkRequest;
     31 import android.net.NetworkScoreManager;
     32 import android.net.ScoredNetwork;
     33 import android.net.wifi.ScanResult;
     34 import android.net.wifi.WifiConfiguration;
     35 import android.net.wifi.WifiInfo;
     36 import android.net.wifi.WifiManager;
     37 import android.net.wifi.WifiNetworkScoreCache;
     38 import android.net.wifi.WifiNetworkScoreCache.CacheListener;
     39 import android.os.ConditionVariable;
     40 import android.os.Handler;
     41 import android.os.Looper;
     42 import android.os.Message;
     43 import android.provider.Settings;
     44 import android.support.annotation.GuardedBy;
     45 import android.util.ArraySet;
     46 import android.util.Log;
     47 import android.util.SparseArray;
     48 import android.util.SparseIntArray;
     49 import android.widget.Toast;
     50 
     51 import com.android.internal.annotations.VisibleForTesting;
     52 import com.android.settingslib.R;
     53 
     54 import java.io.PrintWriter;
     55 import java.util.ArrayList;
     56 import java.util.Collection;
     57 import java.util.Collections;
     58 import java.util.HashMap;
     59 import java.util.Iterator;
     60 import java.util.List;
     61 import java.util.Map;
     62 import java.util.Set;
     63 import java.util.concurrent.atomic.AtomicBoolean;
     64 
     65 /**
     66  * Tracks saved or available wifi networks and their state.
     67  */
     68 public class WifiTracker {
     69     // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
     70 
     71     private static final String TAG = "WifiTracker";
     72     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     73 
     74     /** verbose logging flag. this flag is set thru developer debugging options
     75      * and used so as to assist with in-the-field WiFi connectivity debugging  */
     76     public static int sVerboseLogging = 0;
     77 
     78     // TODO: Allow control of this?
     79     // Combo scans can take 5-6s to complete - set to 10s.
     80     private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
     81     private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
     82 
     83     private final Context mContext;
     84     private final WifiManager mWifiManager;
     85     private final IntentFilter mFilter;
     86     private final ConnectivityManager mConnectivityManager;
     87     private final NetworkRequest mNetworkRequest;
     88     private final AtomicBoolean mConnected = new AtomicBoolean(false);
     89     private final WifiListener mListener;
     90     private final boolean mIncludeSaved;
     91     private final boolean mIncludeScans;
     92     private final boolean mIncludePasspoints;
     93     @VisibleForTesting final MainHandler mMainHandler;
     94     private final WorkHandler mWorkHandler;
     95 
     96     private WifiTrackerNetworkCallback mNetworkCallback;
     97 
     98     @GuardedBy("mLock")
     99     private boolean mRegistered;
    100 
    101     /**
    102      * The externally visible access point list.
    103      *
    104      * Updated using main handler. Clone of this collection is returned from
    105      * {@link #getAccessPoints()}
    106      */
    107     private final List<AccessPoint> mAccessPoints = new ArrayList<>();
    108 
    109     /**
    110      * The internal list of access points, synchronized on itself.
    111      *
    112      * Never exposed outside this class.
    113      */
    114     @GuardedBy("mLock")
    115     private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
    116 
    117     /**
    118     * Synchronization lock for managing concurrency between main and worker threads.
    119     *
    120     * <p>This lock should be held for all background work.
    121     * TODO(b/37674366): Remove the worker thread so synchronization is no longer necessary.
    122     */
    123     private final Object mLock = new Object();
    124 
    125     //visible to both worker and main thread.
    126     @GuardedBy("mLock")
    127     private final AccessPointListenerAdapter mAccessPointListenerAdapter
    128             = new AccessPointListenerAdapter();
    129 
    130     private final HashMap<String, Integer> mSeenBssids = new HashMap<>();
    131     private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
    132     private Integer mScanId = 0;
    133 
    134     private NetworkInfo mLastNetworkInfo;
    135     private WifiInfo mLastInfo;
    136 
    137     private final NetworkScoreManager mNetworkScoreManager;
    138     private final WifiNetworkScoreCache mScoreCache;
    139 
    140     @GuardedBy("mLock")
    141     private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
    142 
    143     @VisibleForTesting
    144     Scanner mScanner;
    145     private boolean mStaleScanResults = false;
    146 
    147     public WifiTracker(Context context, WifiListener wifiListener,
    148             boolean includeSaved, boolean includeScans) {
    149         this(context, wifiListener, null, includeSaved, includeScans);
    150     }
    151 
    152     public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
    153             boolean includeSaved, boolean includeScans) {
    154         this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
    155     }
    156 
    157     public WifiTracker(Context context, WifiListener wifiListener,
    158             boolean includeSaved, boolean includeScans, boolean includePasspoints) {
    159         this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
    160     }
    161 
    162     public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
    163             boolean includeSaved, boolean includeScans, boolean includePasspoints) {
    164         this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
    165                 context.getSystemService(WifiManager.class),
    166                 context.getSystemService(ConnectivityManager.class),
    167                 context.getSystemService(NetworkScoreManager.class), Looper.myLooper()
    168         );
    169     }
    170 
    171     @VisibleForTesting
    172     WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
    173             boolean includeSaved, boolean includeScans, boolean includePasspoints,
    174             WifiManager wifiManager, ConnectivityManager connectivityManager,
    175             NetworkScoreManager networkScoreManager, Looper currentLooper) {
    176         if (!includeSaved && !includeScans) {
    177             throw new IllegalArgumentException("Must include either saved or scans");
    178         }
    179         mContext = context;
    180         if (currentLooper == null) {
    181             // When we aren't on a looper thread, default to the main.
    182             currentLooper = Looper.getMainLooper();
    183         }
    184         mMainHandler = new MainHandler(currentLooper);
    185         mWorkHandler = new WorkHandler(
    186                 workerLooper != null ? workerLooper : currentLooper);
    187         mWifiManager = wifiManager;
    188         mIncludeSaved = includeSaved;
    189         mIncludeScans = includeScans;
    190         mIncludePasspoints = includePasspoints;
    191         mListener = wifiListener;
    192         mConnectivityManager = connectivityManager;
    193 
    194         // check if verbose logging has been turned on or off
    195         sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
    196 
    197         mFilter = new IntentFilter();
    198         mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    199         mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    200         mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
    201         mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    202         mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
    203         mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
    204         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    205         mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
    206 
    207         mNetworkRequest = new NetworkRequest.Builder()
    208                 .clearCapabilities()
    209                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    210                 .build();
    211 
    212         mNetworkScoreManager = networkScoreManager;
    213 
    214         mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
    215             @Override
    216             public void networkCacheUpdated(List<ScoredNetwork> networks) {
    217                 synchronized (mLock) {
    218                     if (!mRegistered) return;
    219                 }
    220 
    221                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    222                     Log.v(TAG, "Score cache was updated with networks: " + networks);
    223                 }
    224                 updateNetworkScores();
    225             }
    226         });
    227     }
    228 
    229     /**
    230      * Synchronously update the list of access points with the latest information.
    231      */
    232     @MainThread
    233     public void forceUpdate() {
    234         synchronized (mLock) {
    235             mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
    236             mLastInfo = mWifiManager.getConnectionInfo();
    237             mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
    238             updateAccessPointsLocked();
    239 
    240             if (DBG) {
    241                 Log.d(TAG, "force update - internal access point list:\n" + mInternalAccessPoints);
    242             }
    243 
    244             // Synchronously copy access points
    245             mMainHandler.removeMessages(MainHandler.MSG_ACCESS_POINT_CHANGED);
    246             mMainHandler.handleMessage(
    247                     Message.obtain(mMainHandler, MainHandler.MSG_ACCESS_POINT_CHANGED));
    248             if (DBG) {
    249                 Log.d(TAG, "force update - external access point list:\n" + mAccessPoints);
    250             }
    251         }
    252     }
    253 
    254     /**
    255      * Force a scan for wifi networks to happen now.
    256      */
    257     public void forceScan() {
    258         if (mWifiManager.isWifiEnabled() && mScanner != null) {
    259             mScanner.forceScan();
    260         }
    261     }
    262 
    263     /**
    264      * Temporarily stop scanning for wifi networks.
    265      */
    266     public void pauseScanning() {
    267         if (mScanner != null) {
    268             mScanner.pause();
    269             mScanner = null;
    270         }
    271     }
    272 
    273     /**
    274      * Resume scanning for wifi networks after it has been paused.
    275      *
    276      * <p>The score cache should be registered before this method is invoked.
    277      */
    278     public void resumeScanning() {
    279         if (mScanner == null) {
    280             mScanner = new Scanner();
    281         }
    282 
    283         mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
    284         if (mWifiManager.isWifiEnabled()) {
    285             mScanner.resume();
    286         }
    287     }
    288 
    289     /**
    290      * Start tracking wifi networks and scores.
    291      *
    292      * <p>Registers listeners and starts scanning for wifi networks. If this is not called
    293      * then forceUpdate() must be called to populate getAccessPoints().
    294      */
    295     @MainThread
    296     public void startTracking() {
    297         synchronized (mLock) {
    298             registerScoreCache();
    299 
    300             resumeScanning();
    301             if (!mRegistered) {
    302                 mContext.registerReceiver(mReceiver, mFilter);
    303                 // NetworkCallback objects cannot be reused. http://b/20701525 .
    304                 mNetworkCallback = new WifiTrackerNetworkCallback();
    305                 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
    306                 mRegistered = true;
    307             }
    308         }
    309     }
    310 
    311     private void registerScoreCache() {
    312         mNetworkScoreManager.registerNetworkScoreCache(
    313                 NetworkKey.TYPE_WIFI,
    314                 mScoreCache,
    315                 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
    316     }
    317 
    318     private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
    319         if (keys.isEmpty()) return;
    320 
    321         if (DBG) {
    322             Log.d(TAG, "Requesting scores for Network Keys: " + keys);
    323         }
    324         mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
    325         synchronized (mLock) {
    326             mRequestedScores.addAll(keys);
    327         }
    328     }
    329 
    330     /**
    331      * Stop tracking wifi networks and scores.
    332      *
    333      * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
    334      * ensure proper cleanup and prevent any further callbacks from occurring.
    335      *
    336      * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
    337      * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
    338      * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
    339      */
    340     @MainThread
    341     public void stopTracking() {
    342         synchronized (mLock) {
    343             if (mRegistered) {
    344                 mContext.unregisterReceiver(mReceiver);
    345                 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
    346                 mRegistered = false;
    347             }
    348             unregisterAndClearScoreCache();
    349             pauseScanning();
    350 
    351             mWorkHandler.removePendingMessages();
    352             mMainHandler.removePendingMessages();
    353         }
    354         mStaleScanResults = true;
    355     }
    356 
    357     private void unregisterAndClearScoreCache() {
    358         mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
    359         mScoreCache.clearScores();
    360 
    361         // Synchronize on mLock to avoid concurrent modification during updateAccessPointsLocked
    362         synchronized (mLock) {
    363             mRequestedScores.clear();
    364         }
    365     }
    366 
    367     /**
    368      * Gets the current list of access points. Should be called from main thread, otherwise
    369      * expect inconsistencies
    370      */
    371     @MainThread
    372     public List<AccessPoint> getAccessPoints() {
    373         return new ArrayList<>(mAccessPoints);
    374     }
    375 
    376     public WifiManager getManager() {
    377         return mWifiManager;
    378     }
    379 
    380     public boolean isWifiEnabled() {
    381         return mWifiManager.isWifiEnabled();
    382     }
    383 
    384     /**
    385      * Returns the number of saved networks on the device, regardless of whether the WifiTracker
    386      * is tracking saved networks.
    387      * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
    388      * directly.
    389      */
    390     public int getNumSavedNetworks() {
    391         return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
    392     }
    393 
    394     public boolean isConnected() {
    395         return mConnected.get();
    396     }
    397 
    398     public void dump(PrintWriter pw) {
    399         pw.println("  - wifi tracker ------");
    400         for (AccessPoint accessPoint : getAccessPoints()) {
    401             pw.println("  " + accessPoint);
    402         }
    403     }
    404 
    405     private void handleResume() {
    406         mScanResultCache.clear();
    407         mSeenBssids.clear();
    408         mScanId = 0;
    409     }
    410 
    411     private Collection<ScanResult> fetchScanResults() {
    412         mScanId++;
    413         final List<ScanResult> newResults = mWifiManager.getScanResults();
    414         for (ScanResult newResult : newResults) {
    415             if (newResult.SSID == null || newResult.SSID.isEmpty()) {
    416                 continue;
    417             }
    418             mScanResultCache.put(newResult.BSSID, newResult);
    419             mSeenBssids.put(newResult.BSSID, mScanId);
    420         }
    421 
    422         if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
    423             if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
    424             Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
    425             for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
    426                     it.hasNext(); /* nothing */) {
    427                 Map.Entry<String, Integer> e = it.next();
    428                 if (e.getValue() < threshold) {
    429                     ScanResult result = mScanResultCache.get(e.getKey());
    430                     if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
    431                     mScanResultCache.remove(e.getKey());
    432                     it.remove();
    433                 }
    434             }
    435             if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
    436         }
    437 
    438         return mScanResultCache.values();
    439     }
    440 
    441     private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) {
    442         final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
    443         if (configs != null) {
    444             for (WifiConfiguration config : configs) {
    445                 if (mLastInfo != null && networkId == config.networkId &&
    446                         !(config.selfAdded && config.numAssociation == 0)) {
    447                     return config;
    448                 }
    449             }
    450         }
    451         return null;
    452     }
    453 
    454     /** Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first. */
    455     private void updateAccessPointsLocked() {
    456         synchronized (mLock) {
    457             updateAccessPoints();
    458         }
    459     }
    460 
    461     /**
    462      * Update the internal list of access points.
    463      *
    464      * <p>Should never be called directly, use {@link #updateAccessPointsLocked()} instead.
    465      */
    466     @GuardedBy("mLock")
    467     private void updateAccessPoints() {
    468         // Swap the current access points into a cached list.
    469         List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
    470         ArrayList<AccessPoint> accessPoints = new ArrayList<>();
    471 
    472         // Clear out the configs so we don't think something is saved when it isn't.
    473         for (AccessPoint accessPoint : cachedAccessPoints) {
    474             accessPoint.clearConfig();
    475         }
    476 
    477     /* Lookup table to more quickly update AccessPoints by only considering objects with the
    478      * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
    479         Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
    480         WifiConfiguration connectionConfig = null;
    481         if (mLastInfo != null) {
    482             connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
    483         }
    484 
    485         final Collection<ScanResult> results = fetchScanResults();
    486 
    487         final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
    488         if (configs != null) {
    489             for (WifiConfiguration config : configs) {
    490                 if (config.selfAdded && config.numAssociation == 0) {
    491                     continue;
    492                 }
    493                 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
    494                 if (mLastInfo != null && mLastNetworkInfo != null) {
    495                     accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
    496                 }
    497                 if (mIncludeSaved) {
    498                     // If saved network not present in scan result then set its Rssi to
    499                     // UNREACHABLE_RSSI
    500                     boolean apFound = false;
    501                     for (ScanResult result : results) {
    502                         if (result.SSID.equals(accessPoint.getSsidStr())) {
    503                             apFound = true;
    504                             break;
    505                         }
    506                     }
    507                     if (!apFound) {
    508                         accessPoint.setUnreachable();
    509                     }
    510                     accessPoints.add(accessPoint);
    511                     apMap.put(accessPoint.getSsidStr(), accessPoint);
    512                 } else {
    513                     // If we aren't using saved networks, drop them into the cache so that
    514                     // we have access to their saved info.
    515                     cachedAccessPoints.add(accessPoint);
    516                 }
    517             }
    518         }
    519 
    520         final List<NetworkKey> scoresToRequest = new ArrayList<>();
    521         if (results != null) {
    522             for (ScanResult result : results) {
    523                 // Ignore hidden and ad-hoc networks.
    524                 if (result.SSID == null || result.SSID.length() == 0 ||
    525                         result.capabilities.contains("[IBSS]")) {
    526                     continue;
    527                 }
    528 
    529                 NetworkKey key = NetworkKey.createFromScanResult(result);
    530                 if (key != null && !mRequestedScores.contains(key)) {
    531                     scoresToRequest.add(key);
    532                 }
    533 
    534                 boolean found = false;
    535                 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
    536                     if (accessPoint.update(result)) {
    537                         found = true;
    538                         break;
    539                     }
    540                 }
    541                 if (!found && mIncludeScans) {
    542                     AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
    543                     if (mLastInfo != null && mLastNetworkInfo != null) {
    544                         accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
    545                     }
    546 
    547                     if (result.isPasspointNetwork()) {
    548                         // Retrieve a WifiConfiguration for a Passpoint provider that matches
    549                         // the given ScanResult.  This is used for showing that a given AP
    550                         // (ScanResult) is available via a Passpoint provider (provider friendly
    551                         // name).
    552                         try {
    553                             WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
    554                             if (config != null) {
    555                                 accessPoint.update(config);
    556                             }
    557                         } catch (UnsupportedOperationException e) {
    558                             // Passpoint not supported on the device.
    559                         }
    560                     }
    561 
    562                     accessPoints.add(accessPoint);
    563                     apMap.put(accessPoint.getSsidStr(), accessPoint);
    564                 }
    565             }
    566         }
    567 
    568         requestScoresForNetworkKeys(scoresToRequest);
    569         for (AccessPoint ap : accessPoints) {
    570             ap.update(mScoreCache, false /* mNetworkScoringUiEnabled */);
    571         }
    572 
    573         // Pre-sort accessPoints to speed preference insertion
    574         Collections.sort(accessPoints);
    575 
    576         // Log accesspoints that were deleted
    577         if (DBG) {
    578             Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
    579             for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
    580                 if (prevAccessPoint.getSsid() == null)
    581                     continue;
    582                 String prevSsid = prevAccessPoint.getSsidStr();
    583                 boolean found = false;
    584                 for (AccessPoint newAccessPoint : accessPoints) {
    585                     if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid()
    586                             .equals(prevSsid)) {
    587                         found = true;
    588                         break;
    589                     }
    590                 }
    591                 if (!found)
    592                     Log.d(TAG, "Did not find " + prevSsid + " in this scan");
    593             }
    594             Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
    595         }
    596 
    597         mInternalAccessPoints.clear();
    598         mInternalAccessPoints.addAll(accessPoints);
    599 
    600         mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
    601     }
    602 
    603     @VisibleForTesting
    604     AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
    605         final int N = cache.size();
    606         for (int i = 0; i < N; i++) {
    607             if (cache.get(i).matches(result)) {
    608                 AccessPoint ret = cache.remove(i);
    609                 ret.update(result);
    610                 return ret;
    611             }
    612         }
    613         final AccessPoint accessPoint = new AccessPoint(mContext, result);
    614         accessPoint.setListener(mAccessPointListenerAdapter);
    615         return accessPoint;
    616     }
    617 
    618     @VisibleForTesting
    619     AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
    620         final int N = cache.size();
    621         for (int i = 0; i < N; i++) {
    622             if (cache.get(i).matches(config)) {
    623                 AccessPoint ret = cache.remove(i);
    624                 ret.loadConfig(config);
    625                 return ret;
    626             }
    627         }
    628         final AccessPoint accessPoint = new AccessPoint(mContext, config);
    629         accessPoint.setListener(mAccessPointListenerAdapter);
    630         return accessPoint;
    631     }
    632 
    633     private void updateNetworkInfo(NetworkInfo networkInfo) {
    634         /* sticky broadcasts can call this when wifi is disabled */
    635         if (!mWifiManager.isWifiEnabled()) {
    636             mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
    637             return;
    638         }
    639 
    640         if (networkInfo != null &&
    641                 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
    642             mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
    643         } else {
    644             mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
    645         }
    646 
    647         if (networkInfo != null) {
    648             mLastNetworkInfo = networkInfo;
    649         }
    650 
    651         WifiConfiguration connectionConfig = null;
    652         mLastInfo = mWifiManager.getConnectionInfo();
    653         if (mLastInfo != null) {
    654             connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
    655         }
    656 
    657         boolean updated = false;
    658         boolean reorder = false; // Only reorder if connected AP was changed
    659 
    660         synchronized (mLock) {
    661             for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
    662                 AccessPoint ap = mInternalAccessPoints.get(i);
    663                 boolean previouslyConnected = ap.isActive();
    664                 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
    665                     updated = true;
    666                     if (previouslyConnected != ap.isActive()) reorder = true;
    667                 }
    668                 if (ap.update(mScoreCache, false /* mNetworkScoringUiEnabled */)) {
    669                     reorder = true;
    670                     updated = true;
    671                 }
    672             }
    673 
    674             if (reorder) Collections.sort(mInternalAccessPoints);
    675 
    676             if (updated) mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
    677         }
    678     }
    679 
    680     /**
    681      * Update all the internal access points rankingScores, badge and metering.
    682      *
    683      * <p>Will trigger a resort and notify listeners of changes if applicable.
    684      *
    685      * <p>Synchronized on {@link #mLock}.
    686      */
    687     private void updateNetworkScores() {
    688         synchronized (mLock) {
    689             boolean updated = false;
    690             for (int i = 0; i < mInternalAccessPoints.size(); i++) {
    691                 if (mInternalAccessPoints.get(i).update(
    692                         mScoreCache, false /* mNetworkScoringUiEnabled */)) {
    693                     updated = true;
    694                 }
    695             }
    696             if (updated) {
    697                 Collections.sort(mInternalAccessPoints);
    698                 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
    699             }
    700         }
    701     }
    702 
    703     private void updateWifiState(int state) {
    704         mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
    705     }
    706 
    707     public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
    708             boolean includeScans, boolean includePasspoints) {
    709         WifiTracker tracker = new WifiTracker(context,
    710                 null, null, includeSaved, includeScans, includePasspoints);
    711         tracker.forceUpdate();
    712         tracker.copyAndNotifyListeners(false /*notifyListeners*/);
    713         return tracker.getAccessPoints();
    714     }
    715 
    716     @VisibleForTesting
    717     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    718         @Override
    719         public void onReceive(Context context, Intent intent) {
    720             String action = intent.getAction();
    721 
    722             if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
    723                 mStaleScanResults = false;
    724             }
    725 
    726             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
    727                 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
    728                         WifiManager.WIFI_STATE_UNKNOWN));
    729             } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
    730                     WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
    731                     WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
    732                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
    733             } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
    734                 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
    735                         WifiManager.EXTRA_NETWORK_INFO);
    736                 mConnected.set(info.isConnected());
    737 
    738                 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
    739 
    740                 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
    741                         .sendToTarget();
    742                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
    743             } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
    744                 NetworkInfo info =
    745                         mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
    746                 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
    747                         .sendToTarget();
    748             }
    749         }
    750     };
    751 
    752     private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
    753         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
    754             if (network.equals(mWifiManager.getCurrentNetwork())) {
    755                 // We don't send a NetworkInfo object along with this message, because even if we
    756                 // fetch one from ConnectivityManager, it might be older than the most recent
    757                 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
    758                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
    759             }
    760         }
    761     }
    762 
    763     @VisibleForTesting
    764     final class MainHandler extends Handler {
    765         @VisibleForTesting static final int MSG_CONNECTED_CHANGED = 0;
    766         @VisibleForTesting static final int MSG_WIFI_STATE_CHANGED = 1;
    767         @VisibleForTesting static final int MSG_ACCESS_POINT_CHANGED = 2;
    768         private static final int MSG_RESUME_SCANNING = 3;
    769         private static final int MSG_PAUSE_SCANNING = 4;
    770 
    771         public MainHandler(Looper looper) {
    772             super(looper);
    773         }
    774 
    775         @Override
    776         public void handleMessage(Message msg) {
    777             if (mListener == null) {
    778                 return;
    779             }
    780             switch (msg.what) {
    781                 case MSG_CONNECTED_CHANGED:
    782                     mListener.onConnectedChanged();
    783                     break;
    784                 case MSG_WIFI_STATE_CHANGED:
    785                     mListener.onWifiStateChanged(msg.arg1);
    786                     break;
    787                 case MSG_ACCESS_POINT_CHANGED:
    788                     copyAndNotifyListeners(true /*notifyListeners*/);
    789                     mListener.onAccessPointsChanged();
    790                     break;
    791                 case MSG_RESUME_SCANNING:
    792                     if (mScanner != null) {
    793                         mScanner.resume();
    794                     }
    795                     break;
    796                 case MSG_PAUSE_SCANNING:
    797                     if (mScanner != null) {
    798                         mScanner.pause();
    799                     }
    800                     break;
    801             }
    802         }
    803 
    804         void removePendingMessages() {
    805             removeMessages(MSG_ACCESS_POINT_CHANGED);
    806             removeMessages(MSG_CONNECTED_CHANGED);
    807             removeMessages(MSG_WIFI_STATE_CHANGED);
    808             removeMessages(MSG_PAUSE_SCANNING);
    809             removeMessages(MSG_RESUME_SCANNING);
    810         }
    811     }
    812 
    813     private final class WorkHandler extends Handler {
    814         private static final int MSG_UPDATE_ACCESS_POINTS = 0;
    815         private static final int MSG_UPDATE_NETWORK_INFO = 1;
    816         private static final int MSG_RESUME = 2;
    817         private static final int MSG_UPDATE_WIFI_STATE = 3;
    818 
    819         public WorkHandler(Looper looper) {
    820             super(looper);
    821         }
    822 
    823         @Override
    824         public void handleMessage(Message msg) {
    825             synchronized (mLock) {
    826                 processMessage(msg);
    827             }
    828         }
    829 
    830         @GuardedBy("mLock")
    831         private void processMessage(Message msg) {
    832             if (!mRegistered) return;
    833 
    834             switch (msg.what) {
    835                 case MSG_UPDATE_ACCESS_POINTS:
    836                     if (!mStaleScanResults) {
    837                         updateAccessPointsLocked();
    838                     }
    839                     break;
    840                 case MSG_UPDATE_NETWORK_INFO:
    841                     updateNetworkInfo((NetworkInfo) msg.obj);
    842                     break;
    843                 case MSG_RESUME:
    844                     handleResume();
    845                     break;
    846                 case MSG_UPDATE_WIFI_STATE:
    847                     if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
    848                         if (mScanner != null) {
    849                             // We only need to resume if mScanner isn't null because
    850                             // that means we want to be scanning.
    851                             mScanner.resume();
    852                         }
    853                     } else {
    854                         mLastInfo = null;
    855                         mLastNetworkInfo = null;
    856                         if (mScanner != null) {
    857                             mScanner.pause();
    858                         }
    859                     }
    860                     mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
    861                             .sendToTarget();
    862                     break;
    863             }
    864         }
    865 
    866         private void removePendingMessages() {
    867             removeMessages(MSG_UPDATE_ACCESS_POINTS);
    868             removeMessages(MSG_UPDATE_NETWORK_INFO);
    869             removeMessages(MSG_RESUME);
    870             removeMessages(MSG_UPDATE_WIFI_STATE);
    871         }
    872     }
    873 
    874     @VisibleForTesting
    875     class Scanner extends Handler {
    876         static final int MSG_SCAN = 0;
    877 
    878         private int mRetry = 0;
    879 
    880         void resume() {
    881             if (!hasMessages(MSG_SCAN)) {
    882                 sendEmptyMessage(MSG_SCAN);
    883             }
    884         }
    885 
    886         void forceScan() {
    887             removeMessages(MSG_SCAN);
    888             sendEmptyMessage(MSG_SCAN);
    889         }
    890 
    891         void pause() {
    892             mRetry = 0;
    893             removeMessages(MSG_SCAN);
    894         }
    895 
    896         @VisibleForTesting
    897         boolean isScanning() {
    898             return hasMessages(MSG_SCAN);
    899         }
    900 
    901         @Override
    902         public void handleMessage(Message message) {
    903             if (message.what != MSG_SCAN) return;
    904             if (mWifiManager.startScan()) {
    905                 mRetry = 0;
    906             } else if (++mRetry >= 3) {
    907                 mRetry = 0;
    908                 if (mContext != null) {
    909                     Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
    910                 }
    911                 return;
    912             }
    913             sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
    914         }
    915     }
    916 
    917     /** A restricted multimap for use in constructAccessPoints */
    918     private static class Multimap<K,V> {
    919         private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
    920         /** retrieve a non-null list of values with key K */
    921         List<V> getAll(K key) {
    922             List<V> values = store.get(key);
    923             return values != null ? values : Collections.<V>emptyList();
    924         }
    925 
    926         void put(K key, V val) {
    927             List<V> curVals = store.get(key);
    928             if (curVals == null) {
    929                 curVals = new ArrayList<V>(3);
    930                 store.put(key, curVals);
    931             }
    932             curVals.add(val);
    933         }
    934     }
    935 
    936     public interface WifiListener {
    937         /**
    938          * Called when the state of Wifi has changed, the state will be one of
    939          * the following.
    940          *
    941          * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
    942          * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
    943          * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
    944          * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
    945          * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
    946          * <p>
    947          *
    948          * @param state The new state of wifi.
    949          */
    950         void onWifiStateChanged(int state);
    951 
    952         /**
    953          * Called when the connection state of wifi has changed and isConnected
    954          * should be called to get the updated state.
    955          */
    956         void onConnectedChanged();
    957 
    958         /**
    959          * Called to indicate the list of AccessPoints has been updated and
    960          * getAccessPoints should be called to get the latest information.
    961          */
    962         void onAccessPointsChanged();
    963     }
    964 
    965     /**
    966      * Helps capture notifications that were generated during AccessPoint modification. Used later
    967      * on by {@link #copyAndNotifyListeners(boolean)} to send notifications.
    968      */
    969     private static class AccessPointListenerAdapter implements AccessPoint.AccessPointListener {
    970         static final int AP_CHANGED = 1;
    971         static final int LEVEL_CHANGED = 2;
    972 
    973         final SparseIntArray mPendingNotifications = new SparseIntArray();
    974 
    975         @Override
    976         public void onAccessPointChanged(AccessPoint accessPoint) {
    977             int type = mPendingNotifications.get(accessPoint.mId);
    978             mPendingNotifications.put(accessPoint.mId, type | AP_CHANGED);
    979         }
    980 
    981         @Override
    982         public void onLevelChanged(AccessPoint accessPoint) {
    983             int type = mPendingNotifications.get(accessPoint.mId);
    984             mPendingNotifications.put(accessPoint.mId, type | LEVEL_CHANGED);
    985         }
    986     }
    987 
    988     /**
    989      * Responsible for copying access points from {@link #mInternalAccessPoints} and notifying
    990      * accesspoint listeners.
    991      *
    992      * @param notifyListeners if true, accesspoint listeners are notified, otherwise notifications
    993      *                        dropped.
    994      */
    995     @MainThread
    996     private void copyAndNotifyListeners(boolean notifyListeners) {
    997         // Need to watch out for memory allocations on main thread.
    998         SparseArray<AccessPoint> oldAccessPoints = new SparseArray<>();
    999         SparseIntArray notificationMap = null;
   1000         List<AccessPoint> updatedAccessPoints = new ArrayList<>();
   1001 
   1002         for (AccessPoint accessPoint : mAccessPoints) {
   1003             oldAccessPoints.put(accessPoint.mId, accessPoint);
   1004         }
   1005 
   1006         if (DBG) {
   1007             Log.d(TAG, "Starting to copy AP items on the MainHandler");
   1008         }
   1009         synchronized (mLock) {
   1010             if (notifyListeners) {
   1011                 notificationMap = mAccessPointListenerAdapter.mPendingNotifications.clone();
   1012             }
   1013 
   1014             mAccessPointListenerAdapter.mPendingNotifications.clear();
   1015 
   1016             for (AccessPoint internalAccessPoint : mInternalAccessPoints) {
   1017                 AccessPoint accessPoint = oldAccessPoints.get(internalAccessPoint.mId);
   1018                 if (accessPoint == null) {
   1019                     accessPoint = new AccessPoint(mContext, internalAccessPoint);
   1020                 } else {
   1021                     accessPoint.copyFrom(internalAccessPoint);
   1022                 }
   1023                 updatedAccessPoints.add(accessPoint);
   1024             }
   1025         }
   1026 
   1027         mAccessPoints.clear();
   1028         mAccessPoints.addAll(updatedAccessPoints);
   1029 
   1030         if (notificationMap != null && notificationMap.size() > 0) {
   1031             for (AccessPoint accessPoint : updatedAccessPoints) {
   1032                 int notificationType = notificationMap.get(accessPoint.mId);
   1033                 AccessPoint.AccessPointListener listener = accessPoint.mAccessPointListener;
   1034                 if (notificationType == 0 || listener == null) {
   1035                     continue;
   1036                 }
   1037 
   1038                 if ((notificationType & AccessPointListenerAdapter.AP_CHANGED) != 0) {
   1039                     listener.onAccessPointChanged(accessPoint);
   1040                 }
   1041 
   1042                 if ((notificationType & AccessPointListenerAdapter.LEVEL_CHANGED) != 0) {
   1043                     listener.onLevelChanged(accessPoint);
   1044                 }
   1045             }
   1046         }
   1047     }
   1048 }
   1049