blob: f7131392c68c452b820a9c458caeb222241bc21d [file] [log] [blame]
Jason Monkd52356a2015-01-28 10:40:41 -05001/*
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 */
16package com.android.settingslib.wifi;
17
Sundeep Ghumanbb399912018-01-29 18:31:15 -080018import android.annotation.AnyThread;
Sundeep Ghumance6bab82017-04-19 16:21:46 -070019import android.annotation.MainThread;
Jason Monkd52356a2015-01-28 10:40:41 -050020import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +090024import android.net.ConnectivityManager;
25import android.net.Network;
26import android.net.NetworkCapabilities;
Jason Monkd52356a2015-01-28 10:40:41 -050027import android.net.NetworkInfo;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080028import android.net.NetworkKey;
Lorenzo Colittiab313f82015-11-26 16:55:15 +090029import android.net.NetworkRequest;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080030import android.net.NetworkScoreManager;
31import android.net.ScoredNetwork;
Jason Monkd52356a2015-01-28 10:40:41 -050032import android.net.wifi.ScanResult;
33import android.net.wifi.WifiConfiguration;
34import android.net.wifi.WifiInfo;
35import android.net.wifi.WifiManager;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080036import android.net.wifi.WifiNetworkScoreCache;
37import android.net.wifi.WifiNetworkScoreCache.CacheListener;
Quang Luong9051dfd2018-12-12 12:49:24 -080038import android.net.wifi.hotspot2.OsuProvider;
Jason Monkd52356a2015-01-28 10:40:41 -050039import android.os.Handler;
Tony Mantler0edf09b2017-09-28 15:03:37 -070040import android.os.HandlerThread;
Jason Monkd52356a2015-01-28 10:40:41 -050041import android.os.Message;
Tony Mantler0edf09b2017-09-28 15:03:37 -070042import android.os.Process;
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080043import android.os.SystemClock;
Sundeep Ghumane869d832017-01-25 16:23:43 -080044import android.provider.Settings;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -070045import android.text.format.DateUtils;
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080046import android.util.ArrayMap;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080047import android.util.ArraySet;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070048import android.util.Log;
Quang Luong7054e922018-12-07 16:44:18 -080049import android.util.Pair;
Jason Monkd52356a2015-01-28 10:40:41 -050050import android.widget.Toast;
51
Fan Zhangf7802ea2018-08-28 15:15:19 -070052import androidx.annotation.GuardedBy;
53import androidx.annotation.NonNull;
54import androidx.annotation.VisibleForTesting;
55
Jason Monkd52356a2015-01-28 10:40:41 -050056import com.android.settingslib.R;
Tony Mantler0edf09b2017-09-28 15:03:37 -070057import com.android.settingslib.core.lifecycle.Lifecycle;
58import com.android.settingslib.core.lifecycle.LifecycleObserver;
59import com.android.settingslib.core.lifecycle.events.OnDestroy;
60import com.android.settingslib.core.lifecycle.events.OnStart;
61import com.android.settingslib.core.lifecycle.events.OnStop;
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -080062import com.android.settingslib.utils.ThreadUtils;
Jason Monkd52356a2015-01-28 10:40:41 -050063
64import java.io.PrintWriter;
65import java.util.ArrayList;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070066import java.util.Collection;
Jason Monkd52356a2015-01-28 10:40:41 -050067import java.util.Collections;
68import java.util.HashMap;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070069import java.util.Iterator;
Jason Monkd52356a2015-01-28 10:40:41 -050070import java.util.List;
Quang Luongac1026e2019-01-02 17:37:01 -080071import java.util.ListIterator;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070072import java.util.Map;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080073import java.util.Set;
Jason Monkd52356a2015-01-28 10:40:41 -050074import java.util.concurrent.atomic.AtomicBoolean;
75
76/**
77 * Tracks saved or available wifi networks and their state.
78 */
Tony Mantler0edf09b2017-09-28 15:03:37 -070079public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -070080 /**
81 * Default maximum age in millis of cached scored networks in
82 * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
83 */
Sundeep Ghuman64bf0902017-09-11 13:00:53 -070084 private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080085
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080086 /** Maximum age of scan results to hold onto while actively scanning. **/
Quang Luong85bbc6e2019-03-28 15:35:22 -070087 @VisibleForTesting static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000;
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080088
Jason Monkd52356a2015-01-28 10:40:41 -050089 private static final String TAG = "WifiTracker";
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -070090 private static final boolean DBG() {
91 return Log.isLoggable(TAG, Log.DEBUG);
92 }
Jason Monkd52356a2015-01-28 10:40:41 -050093
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -080094 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 */
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700105 public static boolean sVerboseLogging;
Jason Monkd52356a2015-01-28 10:40:41 -0500106
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;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900114 private final ConnectivityManager mConnectivityManager;
115 private final NetworkRequest mNetworkRequest;
Jason Monkd52356a2015-01-28 10:40:41 -0500116 private final AtomicBoolean mConnected = new AtomicBoolean(false);
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800117 private final WifiListenerExecutor mListener;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800118 @VisibleForTesting Handler mWorkHandler;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700119 private HandlerThread mWorkThread;
Jason Monk30d80042015-05-08 16:54:18 -0400120
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700121 private WifiTrackerNetworkCallback mNetworkCallback;
122
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800123 /**
124 * Synchronization lock for managing concurrency between main and worker threads.
125 *
cosmohsieh9c227d92019-04-29 14:22:51 +0800126 * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints} and
127 * {@link #mScanner}.
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800128 */
129 private final Object mLock = new Object();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700130
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800131 /** The list of AccessPoints, aggregated visible ScanResults with metadata. */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700132 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700133 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
134
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800135 @GuardedBy("mLock")
136 private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700137
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800138 /**
139 * Tracks whether fresh scan results have been received since scanning start.
140 *
jackqdyulei396e52d2018-09-19 17:28:27 -0700141 * <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.
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800143 */
144 private boolean mStaleScanResults = true;
145
Quang Luong85bbc6e2019-03-28 15:35:22 -0700146 /**
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
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800153 // 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.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700155 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800156 private boolean mRegistered;
Jason Monkd52356a2015-01-28 10:40:41 -0500157
158 private NetworkInfo mLastNetworkInfo;
159 private WifiInfo mLastInfo;
160
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800161 private final NetworkScoreManager mNetworkScoreManager;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700162 private WifiNetworkScoreCache mScoreCache;
Sundeep Ghumane869d832017-01-25 16:23:43 -0800163 private boolean mNetworkScoringUiEnabled;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700164 private long mMaxSpeedLabelScoreCacheAge;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800165
Hai Shalome3cc0682018-11-08 14:21:58 -0800166 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";
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700171
cosmohsieh9c227d92019-04-29 14:22:51 +0800172 @GuardedBy("mLock")
Jason Monkd52356a2015-01-28 10:40:41 -0500173 @VisibleForTesting
174 Scanner mScanner;
Sundeep Ghuman42058742017-07-21 18:42:10 -0700175
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700176 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
Tony Mantler0edf09b2017-09-28 15:03:37 -0700190 /**
191 * Use the lifecycle constructor below whenever possible
192 */
193 @Deprecated
Jason Monk30d80042015-05-08 16:54:18 -0400194 public WifiTracker(Context context, WifiListener wifiListener,
195 boolean includeSaved, boolean includeScans) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700196 this(context, wifiListener,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900197 context.getSystemService(WifiManager.class),
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800198 context.getSystemService(ConnectivityManager.class),
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700199 context.getSystemService(NetworkScoreManager.class),
Tony Mantler0edf09b2017-09-28 15:03:37 -0700200 newIntentFilter());
201 }
202
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700203 // TODO(sghuman): Clean up includeSaved and includeScans from all constructors and linked
Sundeep Ghuman8d157e32018-01-24 20:32:35 -0800204 // calling apps once IC window is complete
Tony Mantler0edf09b2017-09-28 15:03:37 -0700205 public WifiTracker(Context context, WifiListener wifiListener,
206 @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700207 this(context, wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700208 context.getSystemService(WifiManager.class),
209 context.getSystemService(ConnectivityManager.class),
210 context.getSystemService(NetworkScoreManager.class),
211 newIntentFilter());
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700212
Tony Mantler0edf09b2017-09-28 15:03:37 -0700213 lifecycle.addObserver(this);
Jason Monkd52356a2015-01-28 10:40:41 -0500214 }
215
216 @VisibleForTesting
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700217 WifiTracker(Context context, WifiListener wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700218 WifiManager wifiManager, ConnectivityManager connectivityManager,
219 NetworkScoreManager networkScoreManager,
220 IntentFilter filter) {
Jason Monkd52356a2015-01-28 10:40:41 -0500221 mContext = context;
222 mWifiManager = wifiManager;
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700223 mListener = new WifiListenerExecutor(wifiListener);
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900224 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500225
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800226 // check if verbose logging developer option has been turned on or off
Amin Shaikhbc38add2018-12-26 15:31:45 -0500227 sVerboseLogging = mWifiManager != null && (mWifiManager.getVerboseLoggingLevel() > 0);
Jason Monkd52356a2015-01-28 10:40:41 -0500228
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700229 mFilter = filter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900230
231 mNetworkRequest = new NetworkRequest.Builder()
232 .clearCapabilities()
Jeff Sharkeyc159d522018-03-28 10:54:07 -0600233 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900234 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
235 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800236
237 mNetworkScoreManager = networkScoreManager;
238
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800239 // TODO(sghuman): Remove this and create less hacky solution for testing
Tony Mantler0edf09b2017-09-28 15:03:37 -0700240 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
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800252 // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in
253 // during construction
Tony Mantler0edf09b2017-09-28 15:03:37 -0700254 void setWorkThread(HandlerThread workThread) {
255 mWorkThread = workThread;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800256 mWorkHandler = new Handler(workThread.getLooper());
Tony Mantler0edf09b2017-09-28 15:03:37 -0700257 mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800258 @Override
259 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800260 if (!mRegistered) return;
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700261
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800262 if (Log.isLoggable(TAG, Log.VERBOSE)) {
263 Log.v(TAG, "Score cache was updated with networks: " + networks);
264 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700265 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800266 }
267 });
Jason Monkd52356a2015-01-28 10:40:41 -0500268 }
269
Tony Mantler0edf09b2017-09-28 15:03:37 -0700270 @Override
271 public void onDestroy() {
272 mWorkThread.quit();
273 }
274
Jason Monkd52356a2015-01-28 10:40:41 -0500275 /**
Jason Monkd52356a2015-01-28 10:40:41 -0500276 * Temporarily stop scanning for wifi networks.
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800277 *
278 * <p>Sets {@link #mStaleScanResults} to true.
Jason Monkd52356a2015-01-28 10:40:41 -0500279 */
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800280 private void pauseScanning() {
cosmohsieh9c227d92019-04-29 14:22:51 +0800281 synchronized (mLock) {
282 if (mScanner != null) {
283 mScanner.pause();
284 mScanner = null;
285 }
Jason Monkd52356a2015-01-28 10:40:41 -0500286 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800287 mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500288 }
289
290 /**
291 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800292 *
293 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500294 */
295 public void resumeScanning() {
cosmohsieh9c227d92019-04-29 14:22:51 +0800296 synchronized (mLock) {
297 if (mScanner == null) {
298 mScanner = new Scanner();
299 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700300
cosmohsieh9c227d92019-04-29 14:22:51 +0800301 if (isWifiEnabled()) {
302 mScanner.resume();
303 }
Jason Monkd52356a2015-01-28 10:40:41 -0500304 }
Jason Monkd52356a2015-01-28 10:40:41 -0500305 }
306
307 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800308 * Start tracking wifi networks and scores.
309 *
310 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500311 * then forceUpdate() must be called to populate getAccessPoints().
312 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700313 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700314 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700315 public void onStart() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800316 // fetch current ScanResults instead of waiting for broadcast of fresh results
317 forceUpdate();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800318
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800319 registerScoreCache();
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700320
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800321 mNetworkScoringUiEnabled =
322 Settings.Global.getInt(
323 mContext.getContentResolver(),
324 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800325
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800326 mMaxSpeedLabelScoreCacheAge =
327 Settings.Global.getLong(
328 mContext.getContentResolver(),
329 Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
330 DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
Sundeep Ghumanf3421742018-02-08 11:18:42 -0800331
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800332 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();
Erik Kline04ed4302018-04-05 23:28:00 -0700337 mConnectivityManager.registerNetworkCallback(
338 mNetworkRequest, mNetworkCallback, mWorkHandler);
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800339 mRegistered = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500340 }
341 }
342
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800343
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
jackqdyulei396e52d2018-09-19 17:28:27 -0700350 @VisibleForTesting
351 void forceUpdate() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800352 mLastInfo = mWifiManager.getConnectionInfo();
353 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
354
355 fetchScansAndConfigsAndUpdateAccessPoints();
356 }
357
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800358 private void registerScoreCache() {
359 mNetworkScoreManager.registerNetworkScoreCache(
360 NetworkKey.TYPE_WIFI,
361 mScoreCache,
David Sueea09212020-01-13 17:12:27 -0800362 NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800363 }
364
365 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
366 if (keys.isEmpty()) return;
367
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700368 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800369 Log.d(TAG, "Requesting scores for Network Keys: " + keys);
370 }
371 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700372 synchronized (mLock) {
373 mRequestedScores.addAll(keys);
374 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800375 }
376
Jason Monkd52356a2015-01-28 10:40:41 -0500377 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800378 * Stop tracking wifi networks and scores.
379 *
Tony Mantler0edf09b2017-09-28 15:03:37 -0700380 * <p>This should always be called when done with a WifiTracker (if onStart was called) to
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700381 * ensure proper cleanup and prevent any further callbacks from occurring.
382 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700383 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700384 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
385 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
Jason Monkd52356a2015-01-28 10:40:41 -0500386 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700387 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700388 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700389 public void onStop() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800390 if (mRegistered) {
391 mContext.unregisterReceiver(mReceiver);
392 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
393 mRegistered = false;
Jason Monkd52356a2015-01-28 10:40:41 -0500394 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800395 unregisterScoreCache();
396 pauseScanning(); // and set mStaleScanResults
397
398 mWorkHandler.removeCallbacksAndMessages(null /* remove all */);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800399 }
400
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700401 private void unregisterScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800402 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800403
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700404 // 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.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700409 synchronized (mLock) {
410 mRequestedScores.clear();
411 }
Jason Monkd52356a2015-01-28 10:40:41 -0500412 }
413
414 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800415 * 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.
Jason Monkd52356a2015-01-28 10:40:41 -0500419 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800420 @AnyThread
Jason Monkd52356a2015-01-28 10:40:41 -0500421 public List<AccessPoint> getAccessPoints() {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800422 synchronized (mLock) {
423 return new ArrayList<>(mInternalAccessPoints);
424 }
Jason Monkd52356a2015-01-28 10:40:41 -0500425 }
426
427 public WifiManager getManager() {
428 return mWifiManager;
429 }
430
431 public boolean isWifiEnabled() {
Amin Shaikhbc38add2018-12-26 15:31:45 -0500432 return mWifiManager != null && mWifiManager.isWifiEnabled();
Jason Monkd52356a2015-01-28 10:40:41 -0500433 }
434
435 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700436 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
437 * is tracking saved networks.
Peter Qiuabea7262017-05-31 10:18:17 -0700438 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
439 * directly.
Jason Monkd52356a2015-01-28 10:40:41 -0500440 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700441 public int getNumSavedNetworks() {
Peter Qiuabea7262017-05-31 10:18:17 -0700442 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
Jason Monkd52356a2015-01-28 10:40:41 -0500443 }
444
445 public boolean isConnected() {
446 return mConnected.get();
447 }
448
449 public void dump(PrintWriter pw) {
450 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400451 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500452 pw.println(" " + accessPoint);
453 }
454 }
455
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800456 private ArrayMap<String, List<ScanResult>> updateScanResultCache(
457 final List<ScanResult> newResults) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800458 // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for
459 // memory efficiency
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700460 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700461 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
462 continue;
463 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700464 mScanResultCache.put(newResult.BSSID, newResult);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700465 }
466
jackqdyulei396e52d2018-09-19 17:28:27 -0700467 // Evict old results in all conditions
468 evictOldScans();
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700469
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800470 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 }
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800477
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800478 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;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700491 }
492
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800493 /**
Quang Luong85bbc6e2019-03-28 15:35:22 -0700494 * 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.
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800497 *
498 * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when
499 * {@link #mStaleScanResults} is false.
500 */
501 private void evictOldScans() {
Quang Luong85bbc6e2019-03-28 15:35:22 -0700502 long evictionTimeoutMillis = mLastScanSucceeded ? MAX_SCAN_RESULT_AGE_MILLIS
503 : MAX_SCAN_RESULT_AGE_MILLIS * 2;
504
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800505 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
Quang Luong85bbc6e2019-03-28 15:35:22 -0700509 if (nowMs - result.timestamp / 1000 > evictionTimeoutMillis) {
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800510 iter.remove();
511 }
512 }
513 }
514
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700515 private WifiConfiguration getWifiConfigurationForNetworkId(
516 int networkId, final List<WifiConfiguration> configs) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700517 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
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700528 /**
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800529 * Retrieves latest scan results and wifi configs, then calls
530 * {@link #updateAccessPoints(List, List)}.
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700531 */
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800532 private void fetchScansAndConfigsAndUpdateAccessPoints() {
Hai Shalome3cc0682018-11-08 14:21:58 -0800533 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
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800539 if (isVerboseLoggingEnabled()) {
Hai Shalome3cc0682018-11-08 14:21:58 -0800540 Log.i(TAG, "Fetched scan results: " + filteredScanResults);
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700541 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700542
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800543 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
Hai Shalome3cc0682018-11-08 14:21:58 -0800544 updateAccessPoints(filteredScanResults, configs);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700545 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700546
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800547 /** Update the internal list of access points. */
548 private void updateAccessPoints(final List<ScanResult> newScanResults,
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700549 List<WifiConfiguration> configs) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800550
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800551 // Map configs and scan results necessary to make AccessPoints
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800552 final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
Jason Monkd52356a2015-01-28 10:40:41 -0500553 if (configs != null) {
Jason Monkd52356a2015-01-28 10:40:41 -0500554 for (WifiConfiguration config : configs) {
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800555 configsByKey.put(AccessPoint.getKey(config), config);
Jason Monkd52356a2015-01-28 10:40:41 -0500556 }
557 }
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800558
559 WifiConfiguration connectionConfig = null;
560 if (mLastInfo != null) {
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800561 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs);
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800562 }
563
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800564 // 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) {
Quang Luongaf15d3d2019-05-06 16:39:08 -0700567 ArrayMap<String, List<ScanResult>> scanResultsByApKey =
568 updateScanResultCache(newScanResults);
569
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800570 // Swap the current access points into a cached list for maintaining AP listeners
571 List<AccessPoint> cachedAccessPoints;
572 cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500573
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800574 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500575
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800576 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800577
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800578 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);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800583 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700584 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700585
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800586 AccessPoint accessPoint =
587 getCachedOrCreate(entry.getValue(), cachedAccessPoints);
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800588
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
Quang Luong7054e922018-12-07 16:44:18 -0800595 List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values());
596
Quang Luongda49e822019-01-15 14:48:25 -0800597 // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN.
598 accessPoints.addAll(updatePasspointAccessPoints(
599 mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints));
Quang Luong7054e922018-12-07 16:44:18 -0800600
Quang Luongda49e822019-01-15 14:48:25 -0800601 // Add OSU Provider AccessPoints
602 accessPoints.addAll(updateOsuAccessPoints(
603 mWifiManager.getMatchingOsuProviders(cachedScanResults), cachedAccessPoints));
Quang Luong7054e922018-12-07 16:44:18 -0800604
Quang Luongda49e822019-01-15 14:48:25 -0800605 if (mLastInfo != null && mLastNetworkInfo != null) {
606 for (AccessPoint ap : accessPoints) {
607 ap.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Quang Luong7054e922018-12-07 16:44:18 -0800608 }
609 }
610
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800611 // If there were no scan results, create an AP for the currently connected network (if
612 // it exists).
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800613 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
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800620 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()) {
Quang Luong19429e42019-02-20 17:10:51 -0800630 Log.d(TAG,
631 "------ Dumping AccessPoints that were not seen on this scan ------");
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800632 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
Quang Luong19429e42019-02-20 17:10:51 -0800633 String prevTitle = prevAccessPoint.getTitle();
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800634 boolean found = false;
635 for (AccessPoint newAccessPoint : accessPoints) {
Quang Luong19429e42019-02-20 17:10:51 -0800636 if (newAccessPoint.getTitle() != null && newAccessPoint.getTitle()
637 .equals(prevTitle)) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800638 found = true;
639 break;
640 }
641 }
642 if (!found)
Quang Luong19429e42019-02-20 17:10:51 -0800643 Log.d(TAG, "Did not find " + prevTitle + " in this scan");
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800644 }
Quang Luong19429e42019-02-20 17:10:51 -0800645 Log.d(TAG,
646 "---- Done dumping AccessPoints that were not seen on this scan ----");
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800647 }
648
649 mInternalAccessPoints.clear();
650 mInternalAccessPoints.addAll(accessPoints);
651 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700652
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800653 conditionallyNotifyListeners();
Jason Monkd52356a2015-01-28 10:40:41 -0500654 }
655
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700656 @VisibleForTesting
Quang Luongda49e822019-01-15 14:48:25 -0800657 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)) {
Quang Luongda49e822019-01-15 14:48:25 -0800667 List<ScanResult> homeScans =
668 pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
669 List<ScanResult> roamingScans =
670 pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
671
Quang Luongda49e822019-01-15 14:48:25 -0800672 AccessPoint accessPoint =
Quang Luong8123f772019-02-08 16:30:12 -0800673 getCachedOrCreatePasspoint(config, homeScans, roamingScans,
674 accessPointCache);
Quang Luongda49e822019-01-15 14:48:25 -0800675 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 =
Quang Luong8123f772019-02-08 16:30:12 -0800693 getCachedOrCreateOsu(provider, providersAndScans.get(provider),
694 accessPointCache);
Quang Luongda49e822019-01-15 14:48:25 -0800695 accessPoints.add(accessPointOsu);
696 }
697 }
698 return accessPoints;
699 }
700
701 private AccessPoint getCachedOrCreate(
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800702 List<ScanResult> scanResults,
703 List<AccessPoint> cache) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800704 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);
Jason Monkd52356a2015-01-28 10:40:41 -0500709 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800710 return accessPoint;
711 }
712
713 private AccessPoint getCachedOrCreatePasspoint(
Quang Luong8123f772019-02-08 16:30:12 -0800714 WifiConfiguration config,
715 List<ScanResult> homeScans,
716 List<ScanResult> roamingScans,
717 List<AccessPoint> cache) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800718 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config));
719 if (accessPoint == null) {
Quang Luong8123f772019-02-08 16:30:12 -0800720 accessPoint = new AccessPoint(mContext, config, homeScans, roamingScans);
721 } else {
Quang Luonge747f592019-05-02 11:22:33 -0700722 accessPoint.update(config);
Quang Luong8123f772019-02-08 16:30:12 -0800723 accessPoint.setScanResultsPasspoint(homeScans, roamingScans);
Quang Luong1a8fece2019-01-11 14:58:14 -0800724 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700725 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500726 }
727
Quang Luongac1026e2019-01-02 17:37:01 -0800728 private AccessPoint getCachedOrCreateOsu(
Quang Luong8123f772019-02-08 16:30:12 -0800729 OsuProvider provider,
Quang Luongac1026e2019-01-02 17:37:01 -0800730 List<ScanResult> scanResults,
Quang Luong8123f772019-02-08 16:30:12 -0800731 List<AccessPoint> cache) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800732 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider));
733 if (accessPoint == null) {
Quang Luong8123f772019-02-08 16:30:12 -0800734 accessPoint = new AccessPoint(mContext, provider, scanResults);
735 } else {
736 accessPoint.setScanResults(scanResults);
Quang Luong1a8fece2019-01-11 14:58:14 -0800737 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800738 return accessPoint;
739 }
740
741 private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) {
Quang Luongac1026e2019-01-02 17:37:01 -0800742 ListIterator<AccessPoint> lit = cache.listIterator();
743 while (lit.hasNext()) {
Quang Luong550f7d42019-01-09 17:07:19 -0800744 AccessPoint currentAccessPoint = lit.next();
Quang Luong1a8fece2019-01-11 14:58:14 -0800745 if (currentAccessPoint.getKey().equals(key)) {
Quang Luongac1026e2019-01-02 17:37:01 -0800746 lit.remove();
Quang Luong1a8fece2019-01-11 14:58:14 -0800747 return currentAccessPoint;
Quang Luongac1026e2019-01-02 17:37:01 -0800748 }
749 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800750 return null;
Quang Luongac1026e2019-01-02 17:37:01 -0800751 }
752
Jason Monkd52356a2015-01-28 10:40:41 -0500753 private void updateNetworkInfo(NetworkInfo networkInfo) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800754 /* Sticky broadcasts can call this when wifi is disabled */
Amin Shaikhbc38add2018-12-26 15:31:45 -0500755 if (!isWifiEnabled()) {
Sundeep Ghumane8013092017-06-21 22:35:30 -0700756 clearAccessPointsAndConditionallyUpdate();
Jason Monke04ae8a2015-06-03 14:06:34 -0400757 return;
758 }
Jason Monkd52356a2015-01-28 10:40:41 -0500759
Jason Monkd52356a2015-01-28 10:40:41 -0500760 if (networkInfo != null) {
761 mLastNetworkInfo = networkInfo;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700762 if (DBG()) {
763 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo);
764 }
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800765
766 if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) {
767 mListener.onConnectedChanged();
768 }
Jason Monkd52356a2015-01-28 10:40:41 -0500769 }
770
Mitchell Wills5a42db22015-08-03 09:46:08 -0700771 WifiConfiguration connectionConfig = null;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700772
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900773 mLastInfo = mWifiManager.getConnectionInfo();
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700774 if (DBG()) {
775 Log.d(TAG, "mLastInfo set as: " + mLastInfo);
776 }
Mitchell Wills5a42db22015-08-03 09:46:08 -0700777 if (mLastInfo != null) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700778 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
779 mWifiManager.getConfiguredNetworks());
Mitchell Wills5a42db22015-08-03 09:46:08 -0700780 }
781
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700782 boolean updated = false;
783 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700784
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 }
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700793 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700794 reorder = true;
795 updated = true;
796 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800797 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700798
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800799 if (reorder) {
800 Collections.sort(mInternalAccessPoints);
801 }
802 if (updated) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800803 conditionallyNotifyListeners();
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800804 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800805 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800806 }
807
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800808 /**
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 */
Sundeep Ghumane8013092017-06-21 22:35:30 -0700813 private void clearAccessPointsAndConditionallyUpdate() {
814 synchronized (mLock) {
815 if (!mInternalAccessPoints.isEmpty()) {
816 mInternalAccessPoints.clear();
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -0800817 conditionallyNotifyListeners();
Sundeep Ghumane8013092017-06-21 22:35:30 -0700818 }
819 }
820 }
821
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800822 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700823 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800824 *
825 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700826 *
827 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800828 */
829 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700830 synchronized (mLock) {
831 boolean updated = false;
832 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700833 if (mInternalAccessPoints.get(i).update(
834 mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700835 updated = true;
836 }
Jason Monkd52356a2015-01-28 10:40:41 -0500837 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700838 if (updated) {
839 Collections.sort(mInternalAccessPoints);
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800840 conditionallyNotifyListeners();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700841 }
Jason Monkd52356a2015-01-28 10:40:41 -0500842 }
843 }
844
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800845 /**
846 * Receiver for handling broadcasts.
847 *
848 * This receiver is registered on the WorkHandler.
849 */
Jason Monkd52356a2015-01-28 10:40:41 -0500850 @VisibleForTesting
851 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
852 @Override
853 public void onReceive(Context context, Intent intent) {
854 String action = intent.getAction();
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700855
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800856 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;
Quang Luong85bbc6e2019-03-28 15:35:22 -0700862 mLastScanSucceeded =
863 intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800864
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800865 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);
Jason Monkd52356a2015-01-28 10:40:41 -0500879 }
880 }
881 };
882
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800883 /**
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 */
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800889 private void updateWifiState(int state) {
cosmohsieh9c227d92019-04-29 14:22:51 +0800890 if (isVerboseLoggingEnabled()) {
891 Log.d(TAG, "updateWifiState: " + state);
892 }
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800893 if (state == WifiManager.WIFI_STATE_ENABLED) {
cosmohsieh9c227d92019-04-29 14:22:51 +0800894 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 }
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800900 }
901 } else {
902 clearAccessPointsAndConditionallyUpdate();
903 mLastInfo = null;
904 mLastNetworkInfo = null;
cosmohsieh9c227d92019-04-29 14:22:51 +0800905 synchronized (mLock) {
906 if (mScanner != null) {
907 mScanner.pause();
908 }
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800909 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800910 mStaleScanResults = true;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800911 }
912 mListener.onWifiStateChanged(state);
913 }
914
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900915 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
916 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
917 if (network.equals(mWifiManager.getCurrentNetwork())) {
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800918 // TODO(sghuman): Investigate whether this comment still holds true and if it makes
919 // more sense fetch the latest network info here:
920
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900921 // 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.
Erik Kline04ed4302018-04-05 23:28:00 -0700924 updateNetworkInfo(null);
Jason Monk30d80042015-05-08 16:54:18 -0400925 }
926 }
927 }
928
Jason Monkd52356a2015-01-28 10:40:41 -0500929 @VisibleForTesting
930 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400931 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500932
Jason Monkd52356a2015-01-28 10:40:41 -0500933 private int mRetry = 0;
934
935 void resume() {
cosmohsieh9c227d92019-04-29 14:22:51 +0800936 if (isVerboseLoggingEnabled()) {
937 Log.d(TAG, "Scanner resume");
938 }
Jason Monk6572eae2015-02-05 10:34:20 -0500939 if (!hasMessages(MSG_SCAN)) {
940 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500941 }
942 }
943
Jason Monkd52356a2015-01-28 10:40:41 -0500944 void pause() {
cosmohsieh9c227d92019-04-29 14:22:51 +0800945 if (isVerboseLoggingEnabled()) {
946 Log.d(TAG, "Scanner pause");
947 }
Jason Monkd52356a2015-01-28 10:40:41 -0500948 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500949 removeMessages(MSG_SCAN);
950 }
951
952 @VisibleForTesting
953 boolean isScanning() {
954 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500955 }
956
957 @Override
958 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500959 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500960 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 }
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700969 sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS);
Jason Monkd52356a2015-01-28 10:40:41 -0500970 }
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
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800992 /**
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800993 * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800994 *
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800995 * <p>Also logs all callbacks invocations when verbose logging is enabled.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800996 */
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700997 @VisibleForTesting class WifiListenerExecutor implements WifiListener {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800998
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800999 private final WifiListener mDelegatee;
1000
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -08001001 public WifiListenerExecutor(WifiListener listener) {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -08001002 mDelegatee = listener;
1003 }
1004
1005 @Override
1006 public void onWifiStateChanged(int state) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -07001007 runAndLog(() -> mDelegatee.onWifiStateChanged(state),
1008 String.format("Invoking onWifiStateChanged callback with state %d", state));
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -08001009 }
1010
1011 @Override
1012 public void onConnectedChanged() {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -07001013 runAndLog(mDelegatee::onConnectedChanged, "Invoking onConnectedChanged callback");
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -08001014 }
1015
1016 @Override
1017 public void onAccessPointsChanged() {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -07001018 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 });
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -08001030 }
1031 }
1032
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -08001033 /**
1034 * WifiListener interface that defines callbacks indicating state changes in WifiTracker.
1035 *
1036 * <p>All callbacks are invoked on the MainThread.
1037 */
Jason Monkd52356a2015-01-28 10:40:41 -05001038 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 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001055 * Called when the connection state of wifi has changed and
1056 * {@link WifiTracker#isConnected()} should be called to get the updated state.
Jason Monkd52356a2015-01-28 10:40:41 -05001057 */
1058 void onConnectedChanged();
1059
1060 /**
1061 * Called to indicate the list of AccessPoints has been updated and
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001062 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
Jason Monkd52356a2015-01-28 10:40:41 -05001063 */
1064 void onAccessPointsChanged();
1065 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001066
1067 /**
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -08001068 * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001069 * is false.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001070 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001071 private void conditionallyNotifyListeners() {
1072 if (mStaleScanResults) {
1073 return;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001074 }
1075
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -08001076 mListener.onAccessPointsChanged();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001077 }
Hai Shalome3cc0682018-11-08 14:21:58 -08001078
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
Hai Shalom68cf6962019-02-28 12:40:40 -08001091 final boolean isOweSupported = mWifiManager.isEnhancedOpenSupported();
Hai Shalome3cc0682018-11-08 14:21:58 -08001092 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 }
Jason Monkd52356a2015-01-28 10:40:41 -05001123}