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.AnyThread;
     19 import android.annotation.MainThread;
     20 import android.content.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.net.ConnectivityManager;
     25 import android.net.Network;
     26 import android.net.NetworkCapabilities;
     27 import android.net.NetworkInfo;
     28 import android.net.NetworkKey;
     29 import android.net.NetworkRequest;
     30 import android.net.NetworkScoreManager;
     31 import android.net.ScoredNetwork;
     32 import android.net.wifi.ScanResult;
     33 import android.net.wifi.WifiConfiguration;
     34 import android.net.wifi.WifiInfo;
     35 import android.net.wifi.WifiManager;
     36 import android.net.wifi.WifiNetworkScoreCache;
     37 import android.net.wifi.WifiNetworkScoreCache.CacheListener;
     38 import android.net.wifi.hotspot2.OsuProvider;
     39 import android.os.Handler;
     40 import android.os.HandlerThread;
     41 import android.os.Message;
     42 import android.os.Process;
     43 import android.os.SystemClock;
     44 import android.provider.Settings;
     45 import android.text.format.DateUtils;
     46 import android.util.ArrayMap;
     47 import android.util.ArraySet;
     48 import android.util.Log;
     49 import android.util.Pair;
     50 import android.widget.Toast;
     51 
     52 import androidx.annotation.GuardedBy;
     53 import androidx.annotation.NonNull;
     54 import androidx.annotation.VisibleForTesting;
     55 
     56 import com.android.settingslib.R;
     57 import com.android.settingslib.core.lifecycle.Lifecycle;
     58 import com.android.settingslib.core.lifecycle.LifecycleObserver;
     59 import com.android.settingslib.core.lifecycle.events.OnDestroy;
     60 import com.android.settingslib.core.lifecycle.events.OnStart;
     61 import com.android.settingslib.core.lifecycle.events.OnStop;
     62 import com.android.settingslib.utils.ThreadUtils;
     63 
     64 import java.io.PrintWriter;
     65 import java.util.ArrayList;
     66 import java.util.Collection;
     67 import java.util.Collections;
     68 import java.util.HashMap;
     69 import java.util.Iterator;
     70 import java.util.List;
     71 import java.util.ListIterator;
     72 import java.util.Map;
     73 import java.util.Set;
     74 import java.util.concurrent.atomic.AtomicBoolean;
     75 
     76 /**
     77  * Tracks saved or available wifi networks and their state.
     78  */
     79 public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
     80     /**
     81      * Default maximum age in millis of cached scored networks in
     82      * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
     83      */
     84     private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS;
     85 
     86     /** Maximum age of scan results to hold onto while actively scanning. **/
     87     @VisibleForTesting static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000;
     88 
     89     private static final String TAG = "WifiTracker";
     90     private static final boolean DBG() {
     91         return Log.isLoggable(TAG, Log.DEBUG);
     92     }
     93 
     94     private static boolean isVerboseLoggingEnabled() {
     95         return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
     96     }
     97 
     98     /**
     99      * Verbose logging flag set thru developer debugging options and used so as to assist with
    100      * in-the-field WiFi connectivity debugging.
    101      *
    102      * <p>{@link #isVerboseLoggingEnabled()} should be read rather than referencing this value
    103      * directly, to ensure adb TAG level verbose settings are respected.
    104      */
    105     public static boolean sVerboseLogging;
    106 
    107     // TODO: Allow control of this?
    108     // Combo scans can take 5-6s to complete - set to 10s.
    109     private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
    110 
    111     private final Context mContext;
    112     private final WifiManager mWifiManager;
    113     private final IntentFilter mFilter;
    114     private final ConnectivityManager mConnectivityManager;
    115     private final NetworkRequest mNetworkRequest;
    116     private final AtomicBoolean mConnected = new AtomicBoolean(false);
    117     private final WifiListenerExecutor mListener;
    118     @VisibleForTesting Handler mWorkHandler;
    119     private HandlerThread mWorkThread;
    120 
    121     private WifiTrackerNetworkCallback mNetworkCallback;
    122 
    123     /**
    124      * Synchronization lock for managing concurrency between main and worker threads.
    125      *
    126      * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints} and
    127      * {@link #mScanner}.
    128      */
    129     private final Object mLock = new Object();
    130 
    131     /** The list of AccessPoints, aggregated visible ScanResults with metadata. */
    132     @GuardedBy("mLock")
    133     private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
    134 
    135     @GuardedBy("mLock")
    136     private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
    137 
    138     /**
    139      * Tracks whether fresh scan results have been received since scanning start.
    140      *
    141      * <p>If this variable is false, we will not invoke callbacks so that we do not
    142      * update the UI with stale data / clear out existing UI elements prematurely.
    143      */
    144     private boolean mStaleScanResults = true;
    145 
    146     /**
    147      * Tracks whether the latest SCAN_RESULTS_AVAILABLE_ACTION contained new scans. If not, then
    148      * we treat the last scan as an aborted scan and increase the eviction timeout window to avoid
    149      * completely flushing the AP list before the next successful scan completes.
    150      */
    151     private boolean mLastScanSucceeded = true;
    152 
    153     // Does not need to be locked as it only updated on the worker thread, with the exception of
    154     // during onStart, which occurs before the receiver is registered on the work handler.
    155     private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
    156     private boolean mRegistered;
    157 
    158     private NetworkInfo mLastNetworkInfo;
    159     private WifiInfo mLastInfo;
    160 
    161     private final NetworkScoreManager mNetworkScoreManager;
    162     private WifiNetworkScoreCache mScoreCache;
    163     private boolean mNetworkScoringUiEnabled;
    164     private long mMaxSpeedLabelScoreCacheAge;
    165 
    166     private static final String WIFI_SECURITY_PSK = "PSK";
    167     private static final String WIFI_SECURITY_EAP = "EAP";
    168     private static final String WIFI_SECURITY_SAE = "SAE";
    169     private static final String WIFI_SECURITY_OWE = "OWE";
    170     private static final String WIFI_SECURITY_SUITE_B_192 = "SUITE_B_192";
    171 
    172     @GuardedBy("mLock")
    173     @VisibleForTesting
    174     Scanner mScanner;
    175 
    176     private static IntentFilter newIntentFilter() {
    177         IntentFilter filter = new IntentFilter();
    178         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    179         filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    180         filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
    181         filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    182         filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
    183         filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
    184         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    185         filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
    186 
    187         return filter;
    188     }
    189 
    190     /**
    191      * Use the lifecycle constructor below whenever possible
    192      */
    193     @Deprecated
    194     public WifiTracker(Context context, WifiListener wifiListener,
    195             boolean includeSaved, boolean includeScans) {
    196         this(context, wifiListener,
    197                 context.getSystemService(WifiManager.class),
    198                 context.getSystemService(ConnectivityManager.class),
    199                 context.getSystemService(NetworkScoreManager.class),
    200                 newIntentFilter());
    201     }
    202 
    203     // TODO(sghuman): Clean up includeSaved and includeScans from all constructors and linked
    204     // calling apps once IC window is complete
    205     public WifiTracker(Context context, WifiListener wifiListener,
    206             @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
    207         this(context, wifiListener,
    208                 context.getSystemService(WifiManager.class),
    209                 context.getSystemService(ConnectivityManager.class),
    210                 context.getSystemService(NetworkScoreManager.class),
    211                 newIntentFilter());
    212 
    213         lifecycle.addObserver(this);
    214     }
    215 
    216     @VisibleForTesting
    217     WifiTracker(Context context, WifiListener wifiListener,
    218             WifiManager wifiManager, ConnectivityManager connectivityManager,
    219             NetworkScoreManager networkScoreManager,
    220             IntentFilter filter) {
    221         mContext = context;
    222         mWifiManager = wifiManager;
    223         mListener = new WifiListenerExecutor(wifiListener);
    224         mConnectivityManager = connectivityManager;
    225 
    226         // check if verbose logging developer option has been turned on or off
    227         sVerboseLogging = mWifiManager != null && (mWifiManager.getVerboseLoggingLevel() > 0);
    228 
    229         mFilter = filter;
    230 
    231         mNetworkRequest = new NetworkRequest.Builder()
    232                 .clearCapabilities()
    233                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    234                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    235                 .build();
    236 
    237         mNetworkScoreManager = networkScoreManager;
    238 
    239         // TODO(sghuman): Remove this and create less hacky solution for testing
    240         final HandlerThread workThread = new HandlerThread(TAG
    241                 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
    242                 Process.THREAD_PRIORITY_BACKGROUND);
    243         workThread.start();
    244         setWorkThread(workThread);
    245     }
    246 
    247     /**
    248      * Sanity warning: this wipes out mScoreCache, so use with extreme caution
    249      * @param workThread substitute Handler thread, for testing purposes only
    250      */
    251     @VisibleForTesting
    252     // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in
    253     // during construction
    254     void setWorkThread(HandlerThread workThread) {
    255         mWorkThread = workThread;
    256         mWorkHandler = new Handler(workThread.getLooper());
    257         mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
    258             @Override
    259             public void networkCacheUpdated(List<ScoredNetwork> networks) {
    260                 if (!mRegistered) return;
    261 
    262                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    263                     Log.v(TAG, "Score cache was updated with networks: " + networks);
    264                 }
    265                 updateNetworkScores();
    266             }
    267         });
    268     }
    269 
    270     @Override
    271     public void onDestroy() {
    272         mWorkThread.quit();
    273     }
    274 
    275     /**
    276      * Temporarily stop scanning for wifi networks.
    277      *
    278      * <p>Sets {@link #mStaleScanResults} to true.
    279      */
    280     private void pauseScanning() {
    281         synchronized (mLock) {
    282             if (mScanner != null) {
    283                 mScanner.pause();
    284                 mScanner = null;
    285             }
    286         }
    287         mStaleScanResults = true;
    288     }
    289 
    290     /**
    291      * Resume scanning for wifi networks after it has been paused.
    292      *
    293      * <p>The score cache should be registered before this method is invoked.
    294      */
    295     public void resumeScanning() {
    296         synchronized (mLock) {
    297             if (mScanner == null) {
    298                 mScanner = new Scanner();
    299             }
    300 
    301             if (isWifiEnabled()) {
    302                 mScanner.resume();
    303             }
    304         }
    305     }
    306 
    307     /**
    308      * Start tracking wifi networks and scores.
    309      *
    310      * <p>Registers listeners and starts scanning for wifi networks. If this is not called
    311      * then forceUpdate() must be called to populate getAccessPoints().
    312      */
    313     @Override
    314     @MainThread
    315     public void onStart() {
    316         // fetch current ScanResults instead of waiting for broadcast of fresh results
    317         forceUpdate();
    318 
    319         registerScoreCache();
    320 
    321         mNetworkScoringUiEnabled =
    322                 Settings.Global.getInt(
    323                         mContext.getContentResolver(),
    324                         Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
    325 
    326         mMaxSpeedLabelScoreCacheAge =
    327                 Settings.Global.getLong(
    328                         mContext.getContentResolver(),
    329                         Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
    330                         DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
    331 
    332         resumeScanning();
    333         if (!mRegistered) {
    334             mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler);
    335             // NetworkCallback objects cannot be reused. http://b/20701525 .
    336             mNetworkCallback = new WifiTrackerNetworkCallback();
    337             mConnectivityManager.registerNetworkCallback(
    338                     mNetworkRequest, mNetworkCallback, mWorkHandler);
    339             mRegistered = true;
    340         }
    341     }
    342 
    343 
    344     /**
    345      * Synchronously update the list of access points with the latest information.
    346      *
    347      * <p>Intended to only be invoked within {@link #onStart()}.
    348      */
    349     @MainThread
    350     @VisibleForTesting
    351     void forceUpdate() {
    352         mLastInfo = mWifiManager.getConnectionInfo();
    353         mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
    354 
    355         fetchScansAndConfigsAndUpdateAccessPoints();
    356     }
    357 
    358     private void registerScoreCache() {
    359         mNetworkScoreManager.registerNetworkScoreCache(
    360                 NetworkKey.TYPE_WIFI,
    361                 mScoreCache,
    362                 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
    363     }
    364 
    365     private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
    366         if (keys.isEmpty()) return;
    367 
    368         if (DBG()) {
    369             Log.d(TAG, "Requesting scores for Network Keys: " + keys);
    370         }
    371         mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
    372         synchronized (mLock) {
    373             mRequestedScores.addAll(keys);
    374         }
    375     }
    376 
    377     /**
    378      * Stop tracking wifi networks and scores.
    379      *
    380      * <p>This should always be called when done with a WifiTracker (if onStart was called) to
    381      * ensure proper cleanup and prevent any further callbacks from occurring.
    382      *
    383      * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
    384      * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
    385      * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
    386      */
    387     @Override
    388     @MainThread
    389     public void onStop() {
    390         if (mRegistered) {
    391             mContext.unregisterReceiver(mReceiver);
    392             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
    393             mRegistered = false;
    394         }
    395         unregisterScoreCache();
    396         pauseScanning(); // and set mStaleScanResults
    397 
    398         mWorkHandler.removeCallbacksAndMessages(null /* remove all */);
    399     }
    400 
    401     private void unregisterScoreCache() {
    402         mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
    403 
    404         // We do not want to clear the existing scores in the cache, as this method is called during
    405         // stop tracking on activity pause. Hence, on resumption we want the ability to show the
    406         // last known, potentially stale, scores. However, by clearing requested scores, the scores
    407         // will be requested again upon resumption of tracking, and if any changes have occurred
    408         // the listeners (UI) will be updated accordingly.
    409         synchronized (mLock) {
    410             mRequestedScores.clear();
    411         }
    412     }
    413 
    414     /**
    415      * Gets the current list of access points.
    416      *
    417      * <p>This method is can be called on an abitrary thread by clients, but is normally called on
    418      * the UI Thread by the rendering App.
    419      */
    420     @AnyThread
    421     public List<AccessPoint> getAccessPoints() {
    422         synchronized (mLock) {
    423             return new ArrayList<>(mInternalAccessPoints);
    424         }
    425     }
    426 
    427     public WifiManager getManager() {
    428         return mWifiManager;
    429     }
    430 
    431     public boolean isWifiEnabled() {
    432         return mWifiManager != null && mWifiManager.isWifiEnabled();
    433     }
    434 
    435     /**
    436      * Returns the number of saved networks on the device, regardless of whether the WifiTracker
    437      * is tracking saved networks.
    438      * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
    439      * directly.
    440      */
    441     public int getNumSavedNetworks() {
    442         return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
    443     }
    444 
    445     public boolean isConnected() {
    446         return mConnected.get();
    447     }
    448 
    449     public void dump(PrintWriter pw) {
    450         pw.println("  - wifi tracker ------");
    451         for (AccessPoint accessPoint : getAccessPoints()) {
    452             pw.println("  " + accessPoint);
    453         }
    454     }
    455 
    456     private ArrayMap<String, List<ScanResult>> updateScanResultCache(
    457             final List<ScanResult> newResults) {
    458         // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for
    459         // memory efficiency
    460         for (ScanResult newResult : newResults) {
    461             if (newResult.SSID == null || newResult.SSID.isEmpty()) {
    462                 continue;
    463             }
    464             mScanResultCache.put(newResult.BSSID, newResult);
    465         }
    466 
    467         // Evict old results in all conditions
    468         evictOldScans();
    469 
    470         ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
    471         for (ScanResult result : mScanResultCache.values()) {
    472             // Ignore hidden and ad-hoc networks.
    473             if (result.SSID == null || result.SSID.length() == 0 ||
    474                     result.capabilities.contains("[IBSS]")) {
    475                 continue;
    476             }
    477 
    478             String apKey = AccessPoint.getKey(result);
    479             List<ScanResult> resultList;
    480             if (scanResultsByApKey.containsKey(apKey)) {
    481                 resultList = scanResultsByApKey.get(apKey);
    482             } else {
    483                 resultList = new ArrayList<>();
    484                 scanResultsByApKey.put(apKey, resultList);
    485             }
    486 
    487             resultList.add(result);
    488         }
    489 
    490         return scanResultsByApKey;
    491     }
    492 
    493     /**
    494      * Remove old scan results from the cache. If {@link #mLastScanSucceeded} is false, then
    495      * increase the timeout window to avoid completely flushing the AP list before the next
    496      * successful scan completes.
    497      *
    498      * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when
    499      * {@link #mStaleScanResults} is false.
    500      */
    501     private void evictOldScans() {
    502         long evictionTimeoutMillis = mLastScanSucceeded ? MAX_SCAN_RESULT_AGE_MILLIS
    503                 : MAX_SCAN_RESULT_AGE_MILLIS * 2;
    504 
    505         long nowMs = SystemClock.elapsedRealtime();
    506         for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
    507             ScanResult result = iter.next();
    508             // result timestamp is in microseconds
    509             if (nowMs - result.timestamp / 1000 > evictionTimeoutMillis) {
    510                 iter.remove();
    511             }
    512         }
    513     }
    514 
    515     private WifiConfiguration getWifiConfigurationForNetworkId(
    516             int networkId, final List<WifiConfiguration> configs) {
    517         if (configs != null) {
    518             for (WifiConfiguration config : configs) {
    519                 if (mLastInfo != null && networkId == config.networkId &&
    520                         !(config.selfAdded && config.numAssociation == 0)) {
    521                     return config;
    522                 }
    523             }
    524         }
    525         return null;
    526     }
    527 
    528     /**
    529      * Retrieves latest scan results and wifi configs, then calls
    530      * {@link #updateAccessPoints(List, List)}.
    531      */
    532     private void fetchScansAndConfigsAndUpdateAccessPoints() {
    533         List<ScanResult> newScanResults = mWifiManager.getScanResults();
    534 
    535         // Filter all unsupported networks from the scan result list
    536         final List<ScanResult> filteredScanResults =
    537                 filterScanResultsByCapabilities(newScanResults);
    538 
    539         if (isVerboseLoggingEnabled()) {
    540             Log.i(TAG, "Fetched scan results: " + filteredScanResults);
    541         }
    542 
    543         List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
    544         updateAccessPoints(filteredScanResults, configs);
    545     }
    546 
    547     /** Update the internal list of access points. */
    548     private void updateAccessPoints(final List<ScanResult> newScanResults,
    549             List<WifiConfiguration> configs) {
    550 
    551         // Map configs and scan results necessary to make AccessPoints
    552         final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
    553         if (configs != null) {
    554             for (WifiConfiguration config : configs) {
    555                 configsByKey.put(AccessPoint.getKey(config), config);
    556             }
    557         }
    558 
    559         WifiConfiguration connectionConfig = null;
    560         if (mLastInfo != null) {
    561             connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs);
    562         }
    563 
    564         // Rather than dropping and reacquiring the lock multiple times in this method, we lock
    565         // once for efficiency of lock acquisition time and readability
    566         synchronized (mLock) {
    567             ArrayMap<String, List<ScanResult>> scanResultsByApKey =
    568                     updateScanResultCache(newScanResults);
    569 
    570             // Swap the current access points into a cached list for maintaining AP listeners
    571             List<AccessPoint> cachedAccessPoints;
    572             cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
    573 
    574             ArrayList<AccessPoint> accessPoints = new ArrayList<>();
    575 
    576             final List<NetworkKey> scoresToRequest = new ArrayList<>();
    577 
    578             for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) {
    579                 for (ScanResult result : entry.getValue()) {
    580                     NetworkKey key = NetworkKey.createFromScanResult(result);
    581                     if (key != null && !mRequestedScores.contains(key)) {
    582                         scoresToRequest.add(key);
    583                     }
    584                 }
    585 
    586                 AccessPoint accessPoint =
    587                         getCachedOrCreate(entry.getValue(), cachedAccessPoints);
    588 
    589                 // Update the matching config if there is one, to populate saved network info
    590                 accessPoint.update(configsByKey.get(entry.getKey()));
    591 
    592                 accessPoints.add(accessPoint);
    593             }
    594 
    595             List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values());
    596 
    597             // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN.
    598             accessPoints.addAll(updatePasspointAccessPoints(
    599                     mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints));
    600 
    601             // Add OSU Provider AccessPoints
    602             accessPoints.addAll(updateOsuAccessPoints(
    603                     mWifiManager.getMatchingOsuProviders(cachedScanResults), cachedAccessPoints));
    604 
    605             if (mLastInfo != null && mLastNetworkInfo != null) {
    606                 for (AccessPoint ap : accessPoints) {
    607                     ap.update(connectionConfig, mLastInfo, mLastNetworkInfo);
    608                 }
    609             }
    610 
    611             // If there were no scan results, create an AP for the currently connected network (if
    612             // it exists).
    613             if (accessPoints.isEmpty() && connectionConfig != null) {
    614                 AccessPoint activeAp = new AccessPoint(mContext, connectionConfig);
    615                 activeAp.update(connectionConfig, mLastInfo, mLastNetworkInfo);
    616                 accessPoints.add(activeAp);
    617                 scoresToRequest.add(NetworkKey.createFromWifiInfo(mLastInfo));
    618             }
    619 
    620             requestScoresForNetworkKeys(scoresToRequest);
    621             for (AccessPoint ap : accessPoints) {
    622                 ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
    623             }
    624 
    625             // Pre-sort accessPoints to speed preference insertion
    626             Collections.sort(accessPoints);
    627 
    628             // Log accesspoints that are being removed
    629             if (DBG()) {
    630                 Log.d(TAG,
    631                         "------ Dumping AccessPoints that were not seen on this scan ------");
    632                 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
    633                     String prevTitle = prevAccessPoint.getTitle();
    634                     boolean found = false;
    635                     for (AccessPoint newAccessPoint : accessPoints) {
    636                         if (newAccessPoint.getTitle() != null && newAccessPoint.getTitle()
    637                                 .equals(prevTitle)) {
    638                             found = true;
    639                             break;
    640                         }
    641                     }
    642                     if (!found)
    643                         Log.d(TAG, "Did not find " + prevTitle + " in this scan");
    644                 }
    645                 Log.d(TAG,
    646                         "---- Done dumping AccessPoints that were not seen on this scan ----");
    647             }
    648 
    649             mInternalAccessPoints.clear();
    650             mInternalAccessPoints.addAll(accessPoints);
    651         }
    652 
    653         conditionallyNotifyListeners();
    654     }
    655 
    656     @VisibleForTesting
    657     List<AccessPoint> updatePasspointAccessPoints(
    658             List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans,
    659             List<AccessPoint> accessPointCache) {
    660         List<AccessPoint> accessPoints = new ArrayList<>();
    661 
    662         Set<String> seenFQDNs = new ArraySet<>();
    663         for (Pair<WifiConfiguration,
    664                 Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) {
    665             WifiConfiguration config = pairing.first;
    666             if (seenFQDNs.add(config.FQDN)) {
    667                 List<ScanResult> homeScans =
    668                         pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
    669                 List<ScanResult> roamingScans =
    670                         pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
    671 
    672                 AccessPoint accessPoint =
    673                         getCachedOrCreatePasspoint(config, homeScans, roamingScans,
    674                                 accessPointCache);
    675                 accessPoints.add(accessPoint);
    676             }
    677         }
    678         return accessPoints;
    679     }
    680 
    681     @VisibleForTesting
    682     List<AccessPoint> updateOsuAccessPoints(
    683             Map<OsuProvider, List<ScanResult>> providersAndScans,
    684             List<AccessPoint> accessPointCache) {
    685         List<AccessPoint> accessPoints = new ArrayList<>();
    686 
    687         Set<OsuProvider> alreadyProvisioned = mWifiManager
    688                 .getMatchingPasspointConfigsForOsuProviders(
    689                         providersAndScans.keySet()).keySet();
    690         for (OsuProvider provider : providersAndScans.keySet()) {
    691             if (!alreadyProvisioned.contains(provider)) {
    692                 AccessPoint accessPointOsu =
    693                         getCachedOrCreateOsu(provider, providersAndScans.get(provider),
    694                                 accessPointCache);
    695                 accessPoints.add(accessPointOsu);
    696             }
    697         }
    698         return accessPoints;
    699     }
    700 
    701     private AccessPoint getCachedOrCreate(
    702             List<ScanResult> scanResults,
    703             List<AccessPoint> cache) {
    704         AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0)));
    705         if (accessPoint == null) {
    706             accessPoint = new AccessPoint(mContext, scanResults);
    707         } else {
    708             accessPoint.setScanResults(scanResults);
    709         }
    710         return accessPoint;
    711     }
    712 
    713     private AccessPoint getCachedOrCreatePasspoint(
    714             WifiConfiguration config,
    715             List<ScanResult> homeScans,
    716             List<ScanResult> roamingScans,
    717             List<AccessPoint> cache) {
    718         AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config));
    719         if (accessPoint == null) {
    720             accessPoint = new AccessPoint(mContext, config, homeScans, roamingScans);
    721         } else {
    722             accessPoint.update(config);
    723             accessPoint.setScanResultsPasspoint(homeScans, roamingScans);
    724         }
    725         return accessPoint;
    726     }
    727 
    728     private AccessPoint getCachedOrCreateOsu(
    729             OsuProvider provider,
    730             List<ScanResult> scanResults,
    731             List<AccessPoint> cache) {
    732         AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider));
    733         if (accessPoint == null) {
    734             accessPoint = new AccessPoint(mContext, provider, scanResults);
    735         } else {
    736             accessPoint.setScanResults(scanResults);
    737         }
    738         return accessPoint;
    739     }
    740 
    741     private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) {
    742         ListIterator<AccessPoint> lit = cache.listIterator();
    743         while (lit.hasNext()) {
    744             AccessPoint currentAccessPoint = lit.next();
    745             if (currentAccessPoint.getKey().equals(key)) {
    746                 lit.remove();
    747                 return currentAccessPoint;
    748             }
    749         }
    750         return null;
    751     }
    752 
    753     private void updateNetworkInfo(NetworkInfo networkInfo) {
    754         /* Sticky broadcasts can call this when wifi is disabled */
    755         if (!isWifiEnabled()) {
    756             clearAccessPointsAndConditionallyUpdate();
    757             return;
    758         }
    759 
    760         if (networkInfo != null) {
    761             mLastNetworkInfo = networkInfo;
    762             if (DBG()) {
    763                 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo);
    764             }
    765 
    766             if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) {
    767                 mListener.onConnectedChanged();
    768             }
    769         }
    770 
    771         WifiConfiguration connectionConfig = null;
    772 
    773         mLastInfo = mWifiManager.getConnectionInfo();
    774         if (DBG()) {
    775             Log.d(TAG, "mLastInfo set as: " + mLastInfo);
    776         }
    777         if (mLastInfo != null) {
    778             connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
    779                     mWifiManager.getConfiguredNetworks());
    780         }
    781 
    782         boolean updated = false;
    783         boolean reorder = false; // Only reorder if connected AP was changed
    784 
    785         synchronized (mLock) {
    786             for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
    787                 AccessPoint ap = mInternalAccessPoints.get(i);
    788                 boolean previouslyConnected = ap.isActive();
    789                 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
    790                     updated = true;
    791                     if (previouslyConnected != ap.isActive()) reorder = true;
    792                 }
    793                 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
    794                     reorder = true;
    795                     updated = true;
    796                 }
    797             }
    798 
    799             if (reorder) {
    800                 Collections.sort(mInternalAccessPoints);
    801             }
    802             if (updated) {
    803                 conditionallyNotifyListeners();
    804             }
    805         }
    806     }
    807 
    808     /**
    809      * Clears the access point list and conditionally invokes
    810      * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already
    811      * empty).
    812      */
    813     private void clearAccessPointsAndConditionallyUpdate() {
    814         synchronized (mLock) {
    815             if (!mInternalAccessPoints.isEmpty()) {
    816                 mInternalAccessPoints.clear();
    817                 conditionallyNotifyListeners();
    818             }
    819         }
    820     }
    821 
    822     /**
    823      * Update all the internal access points rankingScores, badge and metering.
    824      *
    825      * <p>Will trigger a resort and notify listeners of changes if applicable.
    826      *
    827      * <p>Synchronized on {@link #mLock}.
    828      */
    829     private void updateNetworkScores() {
    830         synchronized (mLock) {
    831             boolean updated = false;
    832             for (int i = 0; i < mInternalAccessPoints.size(); i++) {
    833                 if (mInternalAccessPoints.get(i).update(
    834                         mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
    835                     updated = true;
    836                 }
    837             }
    838             if (updated) {
    839                 Collections.sort(mInternalAccessPoints);
    840                 conditionallyNotifyListeners();
    841             }
    842         }
    843     }
    844 
    845     /**
    846      *  Receiver for handling broadcasts.
    847      *
    848      *  This receiver is registered on the WorkHandler.
    849      */
    850     @VisibleForTesting
    851     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    852         @Override
    853         public void onReceive(Context context, Intent intent) {
    854             String action = intent.getAction();
    855 
    856             if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
    857                 updateWifiState(
    858                         intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
    859                                 WifiManager.WIFI_STATE_UNKNOWN));
    860             } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
    861                 mStaleScanResults = false;
    862                 mLastScanSucceeded =
    863                         intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);
    864 
    865                 fetchScansAndConfigsAndUpdateAccessPoints();
    866             } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
    867                     || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
    868                 fetchScansAndConfigsAndUpdateAccessPoints();
    869             } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
    870                 // TODO(sghuman): Refactor these methods so they cannot result in duplicate
    871                 // onAccessPointsChanged updates being called from this intent.
    872                 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
    873                 updateNetworkInfo(info);
    874                 fetchScansAndConfigsAndUpdateAccessPoints();
    875             } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
    876                 NetworkInfo info =
    877                         mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
    878                 updateNetworkInfo(info);
    879             }
    880         }
    881     };
    882 
    883     /**
    884      * Handles updates to WifiState.
    885      *
    886      * <p>If Wifi is not enabled in the enabled state, {@link #mStaleScanResults} will be set to
    887      * true.
    888      */
    889     private void updateWifiState(int state) {
    890         if (isVerboseLoggingEnabled()) {
    891             Log.d(TAG, "updateWifiState: " + state);
    892         }
    893         if (state == WifiManager.WIFI_STATE_ENABLED) {
    894             synchronized (mLock) {
    895                 if (mScanner != null) {
    896                     // We only need to resume if mScanner isn't null because
    897                     // that means we want to be scanning.
    898                     mScanner.resume();
    899                 }
    900             }
    901         } else {
    902             clearAccessPointsAndConditionallyUpdate();
    903             mLastInfo = null;
    904             mLastNetworkInfo = null;
    905             synchronized (mLock) {
    906                 if (mScanner != null) {
    907                     mScanner.pause();
    908                 }
    909             }
    910             mStaleScanResults = true;
    911         }
    912         mListener.onWifiStateChanged(state);
    913     }
    914 
    915     private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
    916         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
    917             if (network.equals(mWifiManager.getCurrentNetwork())) {
    918                 // TODO(sghuman): Investigate whether this comment still holds true and if it makes
    919                 // more sense fetch the latest network info here:
    920 
    921                 // We don't send a NetworkInfo object along with this message, because even if we
    922                 // fetch one from ConnectivityManager, it might be older than the most recent
    923                 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
    924                 updateNetworkInfo(null);
    925             }
    926         }
    927     }
    928 
    929     @VisibleForTesting
    930     class Scanner extends Handler {
    931         static final int MSG_SCAN = 0;
    932 
    933         private int mRetry = 0;
    934 
    935         void resume() {
    936             if (isVerboseLoggingEnabled()) {
    937                 Log.d(TAG, "Scanner resume");
    938             }
    939             if (!hasMessages(MSG_SCAN)) {
    940                 sendEmptyMessage(MSG_SCAN);
    941             }
    942         }
    943 
    944         void pause() {
    945             if (isVerboseLoggingEnabled()) {
    946                 Log.d(TAG, "Scanner pause");
    947             }
    948             mRetry = 0;
    949             removeMessages(MSG_SCAN);
    950         }
    951 
    952         @VisibleForTesting
    953         boolean isScanning() {
    954             return hasMessages(MSG_SCAN);
    955         }
    956 
    957         @Override
    958         public void handleMessage(Message message) {
    959             if (message.what != MSG_SCAN) return;
    960             if (mWifiManager.startScan()) {
    961                 mRetry = 0;
    962             } else if (++mRetry >= 3) {
    963                 mRetry = 0;
    964                 if (mContext != null) {
    965                     Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
    966                 }
    967                 return;
    968             }
    969             sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS);
    970         }
    971     }
    972 
    973     /** A restricted multimap for use in constructAccessPoints */
    974     private static class Multimap<K,V> {
    975         private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
    976         /** retrieve a non-null list of values with key K */
    977         List<V> getAll(K key) {
    978             List<V> values = store.get(key);
    979             return values != null ? values : Collections.<V>emptyList();
    980         }
    981 
    982         void put(K key, V val) {
    983             List<V> curVals = store.get(key);
    984             if (curVals == null) {
    985                 curVals = new ArrayList<V>(3);
    986                 store.put(key, curVals);
    987             }
    988             curVals.add(val);
    989         }
    990     }
    991 
    992     /**
    993      * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread.
    994      *
    995      * <p>Also logs all callbacks invocations when verbose logging is enabled.
    996      */
    997     @VisibleForTesting class WifiListenerExecutor implements WifiListener {
    998 
    999         private final WifiListener mDelegatee;
   1000 
   1001         public WifiListenerExecutor(WifiListener listener) {
   1002             mDelegatee = listener;
   1003         }
   1004 
   1005         @Override
   1006         public void onWifiStateChanged(int state) {
   1007             runAndLog(() -> mDelegatee.onWifiStateChanged(state),
   1008                     String.format("Invoking onWifiStateChanged callback with state %d", state));
   1009         }
   1010 
   1011         @Override
   1012         public void onConnectedChanged() {
   1013             runAndLog(mDelegatee::onConnectedChanged, "Invoking onConnectedChanged callback");
   1014         }
   1015 
   1016         @Override
   1017         public void onAccessPointsChanged() {
   1018             runAndLog(mDelegatee::onAccessPointsChanged, "Invoking onAccessPointsChanged callback");
   1019         }
   1020 
   1021         private void runAndLog(Runnable r, String verboseLog) {
   1022             ThreadUtils.postOnMainThread(() -> {
   1023                 if (mRegistered) {
   1024                     if (isVerboseLoggingEnabled()) {
   1025                         Log.i(TAG, verboseLog);
   1026                     }
   1027                     r.run();
   1028                 }
   1029             });
   1030         }
   1031     }
   1032 
   1033     /**
   1034      * WifiListener interface that defines callbacks indicating state changes in WifiTracker.
   1035      *
   1036      * <p>All callbacks are invoked on the MainThread.
   1037      */
   1038     public interface WifiListener {
   1039         /**
   1040          * Called when the state of Wifi has changed, the state will be one of
   1041          * the following.
   1042          *
   1043          * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
   1044          * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
   1045          * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
   1046          * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
   1047          * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
   1048          * <p>
   1049          *
   1050          * @param state The new state of wifi.
   1051          */
   1052         void onWifiStateChanged(int state);
   1053 
   1054         /**
   1055          * Called when the connection state of wifi has changed and
   1056          * {@link WifiTracker#isConnected()} should be called to get the updated state.
   1057          */
   1058         void onConnectedChanged();
   1059 
   1060         /**
   1061          * Called to indicate the list of AccessPoints has been updated and
   1062          * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
   1063          */
   1064         void onAccessPointsChanged();
   1065     }
   1066 
   1067     /**
   1068      * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
   1069      * is false.
   1070      */
   1071     private void conditionallyNotifyListeners() {
   1072         if (mStaleScanResults) {
   1073             return;
   1074         }
   1075 
   1076         mListener.onAccessPointsChanged();
   1077     }
   1078 
   1079     /**
   1080      * Filters unsupported networks from scan results. New WPA3 networks and OWE networks
   1081      * may not be compatible with the device HW/SW.
   1082      * @param scanResults List of scan results
   1083      * @return List of filtered scan results based on local device capabilities
   1084      */
   1085     private List<ScanResult> filterScanResultsByCapabilities(List<ScanResult> scanResults) {
   1086         if (scanResults == null) {
   1087             return null;
   1088         }
   1089 
   1090         // Get and cache advanced capabilities
   1091         final boolean isOweSupported = mWifiManager.isEnhancedOpenSupported();
   1092         final boolean isSaeSupported = mWifiManager.isWpa3SaeSupported();
   1093         final boolean isSuiteBSupported = mWifiManager.isWpa3SuiteBSupported();
   1094 
   1095         List<ScanResult> filteredScanResultList = new ArrayList<>();
   1096 
   1097         // Iterate through the list of scan results and filter out APs which are not
   1098         // compatible with our device.
   1099         for (ScanResult scanResult : scanResults) {
   1100             if (scanResult.capabilities.contains(WIFI_SECURITY_PSK)) {
   1101                 // All devices (today) support RSN-PSK or WPA-PSK
   1102                 // Add this here because some APs may support both PSK and SAE and the check
   1103                 // below will filter it out.
   1104                 filteredScanResultList.add(scanResult);
   1105                 continue;
   1106             }
   1107 
   1108             if ((scanResult.capabilities.contains(WIFI_SECURITY_SUITE_B_192) && !isSuiteBSupported)
   1109                     || (scanResult.capabilities.contains(WIFI_SECURITY_SAE) && !isSaeSupported)
   1110                     || (scanResult.capabilities.contains(WIFI_SECURITY_OWE) && !isOweSupported)) {
   1111                 if (isVerboseLoggingEnabled()) {
   1112                     Log.v(TAG, "filterScanResultsByCapabilities: Filtering SSID "
   1113                             + scanResult.SSID + " with capabilities: " + scanResult.capabilities);
   1114                 }
   1115             } else {
   1116                 // Safe to add
   1117                 filteredScanResultList.add(scanResult);
   1118             }
   1119         }
   1120 
   1121         return filteredScanResultList;
   1122     }
   1123 }
   1124