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