blob: fdc0fd33e6d736b1d82f483354dd628d5ea82386 [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. **/
jackqdyulei396e52d2018-09-19 17:28:27 -070087 private 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 *
126 * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}.
127 */
128 private final Object mLock = new Object();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700129
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800130 /** The list of AccessPoints, aggregated visible ScanResults with metadata. */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700131 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700132 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
133
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800134 @GuardedBy("mLock")
135 private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700136
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800137 /**
138 * Tracks whether fresh scan results have been received since scanning start.
139 *
jackqdyulei396e52d2018-09-19 17:28:27 -0700140 * <p>If this variable is false, we will not invoke callbacks so that we do not
141 * update the UI with stale data / clear out existing UI elements prematurely.
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800142 */
143 private boolean mStaleScanResults = true;
144
145 // Does not need to be locked as it only updated on the worker thread, with the exception of
146 // during onStart, which occurs before the receiver is registered on the work handler.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700147 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800148 private boolean mRegistered;
Jason Monkd52356a2015-01-28 10:40:41 -0500149
150 private NetworkInfo mLastNetworkInfo;
151 private WifiInfo mLastInfo;
152
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800153 private final NetworkScoreManager mNetworkScoreManager;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700154 private WifiNetworkScoreCache mScoreCache;
Sundeep Ghumane869d832017-01-25 16:23:43 -0800155 private boolean mNetworkScoringUiEnabled;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700156 private long mMaxSpeedLabelScoreCacheAge;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800157
Hai Shalome3cc0682018-11-08 14:21:58 -0800158 private static final String WIFI_SECURITY_PSK = "PSK";
159 private static final String WIFI_SECURITY_EAP = "EAP";
160 private static final String WIFI_SECURITY_SAE = "SAE";
161 private static final String WIFI_SECURITY_OWE = "OWE";
162 private static final String WIFI_SECURITY_SUITE_B_192 = "SUITE_B_192";
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700163
Jason Monkd52356a2015-01-28 10:40:41 -0500164 @VisibleForTesting
165 Scanner mScanner;
Sundeep Ghuman42058742017-07-21 18:42:10 -0700166
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700167 private static IntentFilter newIntentFilter() {
168 IntentFilter filter = new IntentFilter();
169 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
170 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
171 filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
172 filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
173 filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
174 filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
175 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
176 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
177
178 return filter;
179 }
180
Tony Mantler0edf09b2017-09-28 15:03:37 -0700181 /**
182 * Use the lifecycle constructor below whenever possible
183 */
184 @Deprecated
Jason Monk30d80042015-05-08 16:54:18 -0400185 public WifiTracker(Context context, WifiListener wifiListener,
186 boolean includeSaved, boolean includeScans) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700187 this(context, wifiListener,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900188 context.getSystemService(WifiManager.class),
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800189 context.getSystemService(ConnectivityManager.class),
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700190 context.getSystemService(NetworkScoreManager.class),
Tony Mantler0edf09b2017-09-28 15:03:37 -0700191 newIntentFilter());
192 }
193
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700194 // TODO(sghuman): Clean up includeSaved and includeScans from all constructors and linked
Sundeep Ghuman8d157e32018-01-24 20:32:35 -0800195 // calling apps once IC window is complete
Tony Mantler0edf09b2017-09-28 15:03:37 -0700196 public WifiTracker(Context context, WifiListener wifiListener,
197 @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700198 this(context, wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700199 context.getSystemService(WifiManager.class),
200 context.getSystemService(ConnectivityManager.class),
201 context.getSystemService(NetworkScoreManager.class),
202 newIntentFilter());
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700203
Tony Mantler0edf09b2017-09-28 15:03:37 -0700204 lifecycle.addObserver(this);
Jason Monkd52356a2015-01-28 10:40:41 -0500205 }
206
207 @VisibleForTesting
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700208 WifiTracker(Context context, WifiListener wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700209 WifiManager wifiManager, ConnectivityManager connectivityManager,
210 NetworkScoreManager networkScoreManager,
211 IntentFilter filter) {
Jason Monkd52356a2015-01-28 10:40:41 -0500212 mContext = context;
213 mWifiManager = wifiManager;
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700214 mListener = new WifiListenerExecutor(wifiListener);
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900215 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500216
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800217 // check if verbose logging developer option has been turned on or off
Amin Shaikhbc38add2018-12-26 15:31:45 -0500218 sVerboseLogging = mWifiManager != null && (mWifiManager.getVerboseLoggingLevel() > 0);
Jason Monkd52356a2015-01-28 10:40:41 -0500219
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700220 mFilter = filter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900221
222 mNetworkRequest = new NetworkRequest.Builder()
223 .clearCapabilities()
Jeff Sharkeyc159d522018-03-28 10:54:07 -0600224 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900225 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
226 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800227
228 mNetworkScoreManager = networkScoreManager;
229
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800230 // TODO(sghuman): Remove this and create less hacky solution for testing
Tony Mantler0edf09b2017-09-28 15:03:37 -0700231 final HandlerThread workThread = new HandlerThread(TAG
232 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
233 Process.THREAD_PRIORITY_BACKGROUND);
234 workThread.start();
235 setWorkThread(workThread);
236 }
237
238 /**
239 * Sanity warning: this wipes out mScoreCache, so use with extreme caution
240 * @param workThread substitute Handler thread, for testing purposes only
241 */
242 @VisibleForTesting
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800243 // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in
244 // during construction
Tony Mantler0edf09b2017-09-28 15:03:37 -0700245 void setWorkThread(HandlerThread workThread) {
246 mWorkThread = workThread;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800247 mWorkHandler = new Handler(workThread.getLooper());
Tony Mantler0edf09b2017-09-28 15:03:37 -0700248 mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800249 @Override
250 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800251 if (!mRegistered) return;
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700252
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800253 if (Log.isLoggable(TAG, Log.VERBOSE)) {
254 Log.v(TAG, "Score cache was updated with networks: " + networks);
255 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700256 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800257 }
258 });
Jason Monkd52356a2015-01-28 10:40:41 -0500259 }
260
Tony Mantler0edf09b2017-09-28 15:03:37 -0700261 @Override
262 public void onDestroy() {
263 mWorkThread.quit();
264 }
265
Jason Monkd52356a2015-01-28 10:40:41 -0500266 /**
Jason Monkd52356a2015-01-28 10:40:41 -0500267 * Temporarily stop scanning for wifi networks.
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800268 *
269 * <p>Sets {@link #mStaleScanResults} to true.
Jason Monkd52356a2015-01-28 10:40:41 -0500270 */
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800271 private void pauseScanning() {
Jason Monkd52356a2015-01-28 10:40:41 -0500272 if (mScanner != null) {
273 mScanner.pause();
Jason Monk6572eae2015-02-05 10:34:20 -0500274 mScanner = null;
Jason Monkd52356a2015-01-28 10:40:41 -0500275 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800276 mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500277 }
278
279 /**
280 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800281 *
282 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500283 */
284 public void resumeScanning() {
Jason Monk6572eae2015-02-05 10:34:20 -0500285 if (mScanner == null) {
286 mScanner = new Scanner();
287 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700288
Amin Shaikhbc38add2018-12-26 15:31:45 -0500289 if (isWifiEnabled()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500290 mScanner.resume();
291 }
Jason Monkd52356a2015-01-28 10:40:41 -0500292 }
293
294 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800295 * Start tracking wifi networks and scores.
296 *
297 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500298 * then forceUpdate() must be called to populate getAccessPoints().
299 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700300 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700301 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700302 public void onStart() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800303 // fetch current ScanResults instead of waiting for broadcast of fresh results
304 forceUpdate();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800305
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800306 registerScoreCache();
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700307
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800308 mNetworkScoringUiEnabled =
309 Settings.Global.getInt(
310 mContext.getContentResolver(),
311 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800312
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800313 mMaxSpeedLabelScoreCacheAge =
314 Settings.Global.getLong(
315 mContext.getContentResolver(),
316 Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
317 DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
Sundeep Ghumanf3421742018-02-08 11:18:42 -0800318
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800319 resumeScanning();
320 if (!mRegistered) {
321 mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler);
322 // NetworkCallback objects cannot be reused. http://b/20701525 .
323 mNetworkCallback = new WifiTrackerNetworkCallback();
Erik Kline04ed4302018-04-05 23:28:00 -0700324 mConnectivityManager.registerNetworkCallback(
325 mNetworkRequest, mNetworkCallback, mWorkHandler);
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800326 mRegistered = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500327 }
328 }
329
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800330
331 /**
332 * Synchronously update the list of access points with the latest information.
333 *
334 * <p>Intended to only be invoked within {@link #onStart()}.
335 */
336 @MainThread
jackqdyulei396e52d2018-09-19 17:28:27 -0700337 @VisibleForTesting
338 void forceUpdate() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800339 mLastInfo = mWifiManager.getConnectionInfo();
340 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
341
342 fetchScansAndConfigsAndUpdateAccessPoints();
343 }
344
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800345 private void registerScoreCache() {
346 mNetworkScoreManager.registerNetworkScoreCache(
347 NetworkKey.TYPE_WIFI,
348 mScoreCache,
349 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
350 }
351
352 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
353 if (keys.isEmpty()) return;
354
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700355 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800356 Log.d(TAG, "Requesting scores for Network Keys: " + keys);
357 }
358 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700359 synchronized (mLock) {
360 mRequestedScores.addAll(keys);
361 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800362 }
363
Jason Monkd52356a2015-01-28 10:40:41 -0500364 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800365 * Stop tracking wifi networks and scores.
366 *
Tony Mantler0edf09b2017-09-28 15:03:37 -0700367 * <p>This should always be called when done with a WifiTracker (if onStart was called) to
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700368 * ensure proper cleanup and prevent any further callbacks from occurring.
369 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700370 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700371 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
372 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
Jason Monkd52356a2015-01-28 10:40:41 -0500373 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700374 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700375 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700376 public void onStop() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800377 if (mRegistered) {
378 mContext.unregisterReceiver(mReceiver);
379 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
380 mRegistered = false;
Jason Monkd52356a2015-01-28 10:40:41 -0500381 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800382 unregisterScoreCache();
383 pauseScanning(); // and set mStaleScanResults
384
385 mWorkHandler.removeCallbacksAndMessages(null /* remove all */);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800386 }
387
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700388 private void unregisterScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800389 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800390
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700391 // We do not want to clear the existing scores in the cache, as this method is called during
392 // stop tracking on activity pause. Hence, on resumption we want the ability to show the
393 // last known, potentially stale, scores. However, by clearing requested scores, the scores
394 // will be requested again upon resumption of tracking, and if any changes have occurred
395 // the listeners (UI) will be updated accordingly.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700396 synchronized (mLock) {
397 mRequestedScores.clear();
398 }
Jason Monkd52356a2015-01-28 10:40:41 -0500399 }
400
401 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800402 * Gets the current list of access points.
403 *
404 * <p>This method is can be called on an abitrary thread by clients, but is normally called on
405 * the UI Thread by the rendering App.
Jason Monkd52356a2015-01-28 10:40:41 -0500406 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800407 @AnyThread
Jason Monkd52356a2015-01-28 10:40:41 -0500408 public List<AccessPoint> getAccessPoints() {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800409 synchronized (mLock) {
410 return new ArrayList<>(mInternalAccessPoints);
411 }
Jason Monkd52356a2015-01-28 10:40:41 -0500412 }
413
414 public WifiManager getManager() {
415 return mWifiManager;
416 }
417
418 public boolean isWifiEnabled() {
Amin Shaikhbc38add2018-12-26 15:31:45 -0500419 return mWifiManager != null && mWifiManager.isWifiEnabled();
Jason Monkd52356a2015-01-28 10:40:41 -0500420 }
421
422 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700423 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
424 * is tracking saved networks.
Peter Qiuabea7262017-05-31 10:18:17 -0700425 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
426 * directly.
Jason Monkd52356a2015-01-28 10:40:41 -0500427 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700428 public int getNumSavedNetworks() {
Peter Qiuabea7262017-05-31 10:18:17 -0700429 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
Jason Monkd52356a2015-01-28 10:40:41 -0500430 }
431
432 public boolean isConnected() {
433 return mConnected.get();
434 }
435
436 public void dump(PrintWriter pw) {
437 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400438 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500439 pw.println(" " + accessPoint);
440 }
441 }
442
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800443 private ArrayMap<String, List<ScanResult>> updateScanResultCache(
444 final List<ScanResult> newResults) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800445 // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for
446 // memory efficiency
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700447 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700448 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
449 continue;
450 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700451 mScanResultCache.put(newResult.BSSID, newResult);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700452 }
453
jackqdyulei396e52d2018-09-19 17:28:27 -0700454 // Evict old results in all conditions
455 evictOldScans();
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700456
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800457 ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
458 for (ScanResult result : mScanResultCache.values()) {
459 // Ignore hidden and ad-hoc networks.
460 if (result.SSID == null || result.SSID.length() == 0 ||
461 result.capabilities.contains("[IBSS]")) {
462 continue;
463 }
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800464
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800465 String apKey = AccessPoint.getKey(result);
466 List<ScanResult> resultList;
467 if (scanResultsByApKey.containsKey(apKey)) {
468 resultList = scanResultsByApKey.get(apKey);
469 } else {
470 resultList = new ArrayList<>();
471 scanResultsByApKey.put(apKey, resultList);
472 }
473
474 resultList.add(result);
475 }
476
477 return scanResultsByApKey;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700478 }
479
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800480 /**
481 * Remove old scan results from the cache.
482 *
483 * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when
484 * {@link #mStaleScanResults} is false.
485 */
486 private void evictOldScans() {
487 long nowMs = SystemClock.elapsedRealtime();
488 for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
489 ScanResult result = iter.next();
490 // result timestamp is in microseconds
491 if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) {
492 iter.remove();
493 }
494 }
495 }
496
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700497 private WifiConfiguration getWifiConfigurationForNetworkId(
498 int networkId, final List<WifiConfiguration> configs) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700499 if (configs != null) {
500 for (WifiConfiguration config : configs) {
501 if (mLastInfo != null && networkId == config.networkId &&
502 !(config.selfAdded && config.numAssociation == 0)) {
503 return config;
504 }
505 }
506 }
507 return null;
508 }
509
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700510 /**
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800511 * Retrieves latest scan results and wifi configs, then calls
512 * {@link #updateAccessPoints(List, List)}.
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700513 */
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800514 private void fetchScansAndConfigsAndUpdateAccessPoints() {
Hai Shalome3cc0682018-11-08 14:21:58 -0800515 List<ScanResult> newScanResults = mWifiManager.getScanResults();
516
517 // Filter all unsupported networks from the scan result list
518 final List<ScanResult> filteredScanResults =
519 filterScanResultsByCapabilities(newScanResults);
520
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800521 if (isVerboseLoggingEnabled()) {
Hai Shalome3cc0682018-11-08 14:21:58 -0800522 Log.i(TAG, "Fetched scan results: " + filteredScanResults);
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700523 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700524
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800525 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
Hai Shalome3cc0682018-11-08 14:21:58 -0800526 updateAccessPoints(filteredScanResults, configs);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700527 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700528
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800529 /** Update the internal list of access points. */
530 private void updateAccessPoints(final List<ScanResult> newScanResults,
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700531 List<WifiConfiguration> configs) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800532
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800533 // Map configs and scan results necessary to make AccessPoints
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800534 final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
Jason Monkd52356a2015-01-28 10:40:41 -0500535 if (configs != null) {
Jason Monkd52356a2015-01-28 10:40:41 -0500536 for (WifiConfiguration config : configs) {
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800537 configsByKey.put(AccessPoint.getKey(config), config);
Jason Monkd52356a2015-01-28 10:40:41 -0500538 }
539 }
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800540 ArrayMap<String, List<ScanResult>> scanResultsByApKey =
541 updateScanResultCache(newScanResults);
542
543 WifiConfiguration connectionConfig = null;
544 if (mLastInfo != null) {
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800545 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs);
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800546 }
547
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800548 // Rather than dropping and reacquiring the lock multiple times in this method, we lock
549 // once for efficiency of lock acquisition time and readability
550 synchronized (mLock) {
551 // Swap the current access points into a cached list for maintaining AP listeners
552 List<AccessPoint> cachedAccessPoints;
553 cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500554
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800555 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500556
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800557 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800558
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800559 for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) {
560 for (ScanResult result : entry.getValue()) {
561 NetworkKey key = NetworkKey.createFromScanResult(result);
562 if (key != null && !mRequestedScores.contains(key)) {
563 scoresToRequest.add(key);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800564 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700565 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700566
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800567 AccessPoint accessPoint =
568 getCachedOrCreate(entry.getValue(), cachedAccessPoints);
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800569
570 // Update the matching config if there is one, to populate saved network info
571 accessPoint.update(configsByKey.get(entry.getKey()));
572
573 accessPoints.add(accessPoint);
574 }
575
Quang Luong7054e922018-12-07 16:44:18 -0800576 List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values());
577
Quang Luongda49e822019-01-15 14:48:25 -0800578 // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN.
579 accessPoints.addAll(updatePasspointAccessPoints(
580 mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints));
Quang Luong7054e922018-12-07 16:44:18 -0800581
Quang Luongda49e822019-01-15 14:48:25 -0800582 // Add OSU Provider AccessPoints
583 accessPoints.addAll(updateOsuAccessPoints(
584 mWifiManager.getMatchingOsuProviders(cachedScanResults), cachedAccessPoints));
Quang Luong7054e922018-12-07 16:44:18 -0800585
Quang Luongda49e822019-01-15 14:48:25 -0800586 if (mLastInfo != null && mLastNetworkInfo != null) {
587 for (AccessPoint ap : accessPoints) {
588 ap.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Quang Luong7054e922018-12-07 16:44:18 -0800589 }
590 }
591
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800592 // If there were no scan results, create an AP for the currently connected network (if
593 // it exists).
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800594 if (accessPoints.isEmpty() && connectionConfig != null) {
595 AccessPoint activeAp = new AccessPoint(mContext, connectionConfig);
596 activeAp.update(connectionConfig, mLastInfo, mLastNetworkInfo);
597 accessPoints.add(activeAp);
598 scoresToRequest.add(NetworkKey.createFromWifiInfo(mLastInfo));
599 }
600
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800601 requestScoresForNetworkKeys(scoresToRequest);
602 for (AccessPoint ap : accessPoints) {
603 ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
604 }
605
606 // Pre-sort accessPoints to speed preference insertion
607 Collections.sort(accessPoints);
608
609 // Log accesspoints that are being removed
610 if (DBG()) {
Quang Luong19429e42019-02-20 17:10:51 -0800611 Log.d(TAG,
612 "------ Dumping AccessPoints that were not seen on this scan ------");
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800613 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
Quang Luong19429e42019-02-20 17:10:51 -0800614 String prevTitle = prevAccessPoint.getTitle();
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800615 boolean found = false;
616 for (AccessPoint newAccessPoint : accessPoints) {
Quang Luong19429e42019-02-20 17:10:51 -0800617 if (newAccessPoint.getTitle() != null && newAccessPoint.getTitle()
618 .equals(prevTitle)) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800619 found = true;
620 break;
621 }
622 }
623 if (!found)
Quang Luong19429e42019-02-20 17:10:51 -0800624 Log.d(TAG, "Did not find " + prevTitle + " in this scan");
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800625 }
Quang Luong19429e42019-02-20 17:10:51 -0800626 Log.d(TAG,
627 "---- Done dumping AccessPoints that were not seen on this scan ----");
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800628 }
629
630 mInternalAccessPoints.clear();
631 mInternalAccessPoints.addAll(accessPoints);
632 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700633
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800634 conditionallyNotifyListeners();
Jason Monkd52356a2015-01-28 10:40:41 -0500635 }
636
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700637 @VisibleForTesting
Quang Luongda49e822019-01-15 14:48:25 -0800638 List<AccessPoint> updatePasspointAccessPoints(
639 List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans,
640 List<AccessPoint> accessPointCache) {
641 List<AccessPoint> accessPoints = new ArrayList<>();
642
643 Set<String> seenFQDNs = new ArraySet<>();
644 for (Pair<WifiConfiguration,
645 Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) {
646 WifiConfiguration config = pairing.first;
647 if (seenFQDNs.add(config.FQDN)) {
Quang Luongda49e822019-01-15 14:48:25 -0800648 List<ScanResult> homeScans =
649 pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
650 List<ScanResult> roamingScans =
651 pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
652
Quang Luongda49e822019-01-15 14:48:25 -0800653 AccessPoint accessPoint =
Quang Luong8123f772019-02-08 16:30:12 -0800654 getCachedOrCreatePasspoint(config, homeScans, roamingScans,
655 accessPointCache);
Quang Luongda49e822019-01-15 14:48:25 -0800656 accessPoints.add(accessPoint);
657 }
658 }
659 return accessPoints;
660 }
661
662 @VisibleForTesting
663 List<AccessPoint> updateOsuAccessPoints(
664 Map<OsuProvider, List<ScanResult>> providersAndScans,
665 List<AccessPoint> accessPointCache) {
666 List<AccessPoint> accessPoints = new ArrayList<>();
667
668 Set<OsuProvider> alreadyProvisioned = mWifiManager
669 .getMatchingPasspointConfigsForOsuProviders(
670 providersAndScans.keySet()).keySet();
671 for (OsuProvider provider : providersAndScans.keySet()) {
672 if (!alreadyProvisioned.contains(provider)) {
673 AccessPoint accessPointOsu =
Quang Luong8123f772019-02-08 16:30:12 -0800674 getCachedOrCreateOsu(provider, providersAndScans.get(provider),
675 accessPointCache);
Quang Luongda49e822019-01-15 14:48:25 -0800676 accessPoints.add(accessPointOsu);
677 }
678 }
679 return accessPoints;
680 }
681
682 private AccessPoint getCachedOrCreate(
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800683 List<ScanResult> scanResults,
684 List<AccessPoint> cache) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800685 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0)));
686 if (accessPoint == null) {
687 accessPoint = new AccessPoint(mContext, scanResults);
688 } else {
689 accessPoint.setScanResults(scanResults);
Jason Monkd52356a2015-01-28 10:40:41 -0500690 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800691 return accessPoint;
692 }
693
694 private AccessPoint getCachedOrCreatePasspoint(
Quang Luong8123f772019-02-08 16:30:12 -0800695 WifiConfiguration config,
696 List<ScanResult> homeScans,
697 List<ScanResult> roamingScans,
698 List<AccessPoint> cache) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800699 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config));
700 if (accessPoint == null) {
Quang Luong8123f772019-02-08 16:30:12 -0800701 accessPoint = new AccessPoint(mContext, config, homeScans, roamingScans);
702 } else {
703 accessPoint.setScanResultsPasspoint(homeScans, roamingScans);
Quang Luong1a8fece2019-01-11 14:58:14 -0800704 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700705 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500706 }
707
Quang Luongac1026e2019-01-02 17:37:01 -0800708 private AccessPoint getCachedOrCreateOsu(
Quang Luong8123f772019-02-08 16:30:12 -0800709 OsuProvider provider,
Quang Luongac1026e2019-01-02 17:37:01 -0800710 List<ScanResult> scanResults,
Quang Luong8123f772019-02-08 16:30:12 -0800711 List<AccessPoint> cache) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800712 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider));
713 if (accessPoint == null) {
Quang Luong8123f772019-02-08 16:30:12 -0800714 accessPoint = new AccessPoint(mContext, provider, scanResults);
715 } else {
716 accessPoint.setScanResults(scanResults);
Quang Luong1a8fece2019-01-11 14:58:14 -0800717 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800718 return accessPoint;
719 }
720
721 private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) {
Quang Luongac1026e2019-01-02 17:37:01 -0800722 ListIterator<AccessPoint> lit = cache.listIterator();
723 while (lit.hasNext()) {
Quang Luong550f7d42019-01-09 17:07:19 -0800724 AccessPoint currentAccessPoint = lit.next();
Quang Luong1a8fece2019-01-11 14:58:14 -0800725 if (currentAccessPoint.getKey().equals(key)) {
Quang Luongac1026e2019-01-02 17:37:01 -0800726 lit.remove();
Quang Luong1a8fece2019-01-11 14:58:14 -0800727 return currentAccessPoint;
Quang Luongac1026e2019-01-02 17:37:01 -0800728 }
729 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800730 return null;
Quang Luongac1026e2019-01-02 17:37:01 -0800731 }
732
Jason Monkd52356a2015-01-28 10:40:41 -0500733 private void updateNetworkInfo(NetworkInfo networkInfo) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800734
735 /* Sticky broadcasts can call this when wifi is disabled */
Amin Shaikhbc38add2018-12-26 15:31:45 -0500736 if (!isWifiEnabled()) {
Sundeep Ghumane8013092017-06-21 22:35:30 -0700737 clearAccessPointsAndConditionallyUpdate();
Jason Monke04ae8a2015-06-03 14:06:34 -0400738 return;
739 }
Jason Monkd52356a2015-01-28 10:40:41 -0500740
Jason Monkd52356a2015-01-28 10:40:41 -0500741 if (networkInfo != null) {
742 mLastNetworkInfo = networkInfo;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700743 if (DBG()) {
744 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo);
745 }
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800746
747 if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) {
748 mListener.onConnectedChanged();
749 }
Jason Monkd52356a2015-01-28 10:40:41 -0500750 }
751
Mitchell Wills5a42db22015-08-03 09:46:08 -0700752 WifiConfiguration connectionConfig = null;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700753
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900754 mLastInfo = mWifiManager.getConnectionInfo();
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700755 if (DBG()) {
756 Log.d(TAG, "mLastInfo set as: " + mLastInfo);
757 }
Mitchell Wills5a42db22015-08-03 09:46:08 -0700758 if (mLastInfo != null) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700759 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
760 mWifiManager.getConfiguredNetworks());
Mitchell Wills5a42db22015-08-03 09:46:08 -0700761 }
762
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700763 boolean updated = false;
764 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700765
766 synchronized (mLock) {
767 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
768 AccessPoint ap = mInternalAccessPoints.get(i);
769 boolean previouslyConnected = ap.isActive();
770 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
771 updated = true;
772 if (previouslyConnected != ap.isActive()) reorder = true;
773 }
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700774 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700775 reorder = true;
776 updated = true;
777 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800778 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700779
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800780 if (reorder) {
781 Collections.sort(mInternalAccessPoints);
782 }
783 if (updated) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800784 conditionallyNotifyListeners();
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800785 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800786 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800787 }
788
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800789 /**
790 * Clears the access point list and conditionally invokes
791 * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already
792 * empty).
793 */
Sundeep Ghumane8013092017-06-21 22:35:30 -0700794 private void clearAccessPointsAndConditionallyUpdate() {
795 synchronized (mLock) {
796 if (!mInternalAccessPoints.isEmpty()) {
797 mInternalAccessPoints.clear();
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -0800798 conditionallyNotifyListeners();
Sundeep Ghumane8013092017-06-21 22:35:30 -0700799 }
800 }
801 }
802
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800803 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700804 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800805 *
806 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700807 *
808 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800809 */
810 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700811 synchronized (mLock) {
812 boolean updated = false;
813 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700814 if (mInternalAccessPoints.get(i).update(
815 mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700816 updated = true;
817 }
Jason Monkd52356a2015-01-28 10:40:41 -0500818 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700819 if (updated) {
820 Collections.sort(mInternalAccessPoints);
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800821 conditionallyNotifyListeners();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700822 }
Jason Monkd52356a2015-01-28 10:40:41 -0500823 }
824 }
825
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800826 /**
827 * Receiver for handling broadcasts.
828 *
829 * This receiver is registered on the WorkHandler.
830 */
Jason Monkd52356a2015-01-28 10:40:41 -0500831 @VisibleForTesting
832 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
833 @Override
834 public void onReceive(Context context, Intent intent) {
835 String action = intent.getAction();
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700836
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800837 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
838 updateWifiState(
839 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
840 WifiManager.WIFI_STATE_UNKNOWN));
841 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
842 mStaleScanResults = false;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800843
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800844 fetchScansAndConfigsAndUpdateAccessPoints();
845 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
846 || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
847 fetchScansAndConfigsAndUpdateAccessPoints();
848 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
849 // TODO(sghuman): Refactor these methods so they cannot result in duplicate
850 // onAccessPointsChanged updates being called from this intent.
851 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
852 updateNetworkInfo(info);
853 fetchScansAndConfigsAndUpdateAccessPoints();
854 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
855 NetworkInfo info =
856 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
857 updateNetworkInfo(info);
Jason Monkd52356a2015-01-28 10:40:41 -0500858 }
859 }
860 };
861
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800862 /**
863 * Handles updates to WifiState.
864 *
865 * <p>If Wifi is not enabled in the enabled state, {@link #mStaleScanResults} will be set to
866 * true.
867 */
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800868 private void updateWifiState(int state) {
869 if (state == WifiManager.WIFI_STATE_ENABLED) {
870 if (mScanner != null) {
871 // We only need to resume if mScanner isn't null because
872 // that means we want to be scanning.
873 mScanner.resume();
874 }
875 } else {
876 clearAccessPointsAndConditionallyUpdate();
877 mLastInfo = null;
878 mLastNetworkInfo = null;
879 if (mScanner != null) {
880 mScanner.pause();
881 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800882 mStaleScanResults = true;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800883 }
884 mListener.onWifiStateChanged(state);
885 }
886
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900887 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
888 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
889 if (network.equals(mWifiManager.getCurrentNetwork())) {
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800890 // TODO(sghuman): Investigate whether this comment still holds true and if it makes
891 // more sense fetch the latest network info here:
892
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900893 // We don't send a NetworkInfo object along with this message, because even if we
894 // fetch one from ConnectivityManager, it might be older than the most recent
895 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
Erik Kline04ed4302018-04-05 23:28:00 -0700896 updateNetworkInfo(null);
Jason Monk30d80042015-05-08 16:54:18 -0400897 }
898 }
899 }
900
Jason Monkd52356a2015-01-28 10:40:41 -0500901 @VisibleForTesting
902 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400903 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500904
Jason Monkd52356a2015-01-28 10:40:41 -0500905 private int mRetry = 0;
906
907 void resume() {
Jason Monk6572eae2015-02-05 10:34:20 -0500908 if (!hasMessages(MSG_SCAN)) {
909 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500910 }
911 }
912
Jason Monkd52356a2015-01-28 10:40:41 -0500913 void pause() {
914 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500915 removeMessages(MSG_SCAN);
916 }
917
918 @VisibleForTesting
919 boolean isScanning() {
920 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500921 }
922
923 @Override
924 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500925 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500926 if (mWifiManager.startScan()) {
927 mRetry = 0;
928 } else if (++mRetry >= 3) {
929 mRetry = 0;
930 if (mContext != null) {
931 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
932 }
933 return;
934 }
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700935 sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS);
Jason Monkd52356a2015-01-28 10:40:41 -0500936 }
937 }
938
939 /** A restricted multimap for use in constructAccessPoints */
940 private static class Multimap<K,V> {
941 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
942 /** retrieve a non-null list of values with key K */
943 List<V> getAll(K key) {
944 List<V> values = store.get(key);
945 return values != null ? values : Collections.<V>emptyList();
946 }
947
948 void put(K key, V val) {
949 List<V> curVals = store.get(key);
950 if (curVals == null) {
951 curVals = new ArrayList<V>(3);
952 store.put(key, curVals);
953 }
954 curVals.add(val);
955 }
956 }
957
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800958 /**
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800959 * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800960 *
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800961 * <p>Also logs all callbacks invocations when verbose logging is enabled.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800962 */
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700963 @VisibleForTesting class WifiListenerExecutor implements WifiListener {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800964
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800965 private final WifiListener mDelegatee;
966
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800967 public WifiListenerExecutor(WifiListener listener) {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800968 mDelegatee = listener;
969 }
970
971 @Override
972 public void onWifiStateChanged(int state) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700973 runAndLog(() -> mDelegatee.onWifiStateChanged(state),
974 String.format("Invoking onWifiStateChanged callback with state %d", state));
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800975 }
976
977 @Override
978 public void onConnectedChanged() {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700979 runAndLog(mDelegatee::onConnectedChanged, "Invoking onConnectedChanged callback");
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800980 }
981
982 @Override
983 public void onAccessPointsChanged() {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700984 runAndLog(mDelegatee::onAccessPointsChanged, "Invoking onAccessPointsChanged callback");
985 }
986
987 private void runAndLog(Runnable r, String verboseLog) {
988 ThreadUtils.postOnMainThread(() -> {
989 if (mRegistered) {
990 if (isVerboseLoggingEnabled()) {
991 Log.i(TAG, verboseLog);
992 }
993 r.run();
994 }
995 });
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800996 }
997 }
998
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800999 /**
1000 * WifiListener interface that defines callbacks indicating state changes in WifiTracker.
1001 *
1002 * <p>All callbacks are invoked on the MainThread.
1003 */
Jason Monkd52356a2015-01-28 10:40:41 -05001004 public interface WifiListener {
1005 /**
1006 * Called when the state of Wifi has changed, the state will be one of
1007 * the following.
1008 *
1009 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
1010 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
1011 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
1012 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
1013 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
1014 * <p>
1015 *
1016 * @param state The new state of wifi.
1017 */
1018 void onWifiStateChanged(int state);
1019
1020 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001021 * Called when the connection state of wifi has changed and
1022 * {@link WifiTracker#isConnected()} should be called to get the updated state.
Jason Monkd52356a2015-01-28 10:40:41 -05001023 */
1024 void onConnectedChanged();
1025
1026 /**
1027 * Called to indicate the list of AccessPoints has been updated and
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001028 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
Jason Monkd52356a2015-01-28 10:40:41 -05001029 */
1030 void onAccessPointsChanged();
1031 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001032
1033 /**
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -08001034 * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001035 * is false.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001036 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001037 private void conditionallyNotifyListeners() {
1038 if (mStaleScanResults) {
1039 return;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001040 }
1041
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -08001042 mListener.onAccessPointsChanged();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001043 }
Hai Shalome3cc0682018-11-08 14:21:58 -08001044
1045 /**
1046 * Filters unsupported networks from scan results. New WPA3 networks and OWE networks
1047 * may not be compatible with the device HW/SW.
1048 * @param scanResults List of scan results
1049 * @return List of filtered scan results based on local device capabilities
1050 */
1051 private List<ScanResult> filterScanResultsByCapabilities(List<ScanResult> scanResults) {
1052 if (scanResults == null) {
1053 return null;
1054 }
1055
1056 // Get and cache advanced capabilities
Hai Shalom68cf6962019-02-28 12:40:40 -08001057 final boolean isOweSupported = mWifiManager.isEnhancedOpenSupported();
Hai Shalome3cc0682018-11-08 14:21:58 -08001058 final boolean isSaeSupported = mWifiManager.isWpa3SaeSupported();
1059 final boolean isSuiteBSupported = mWifiManager.isWpa3SuiteBSupported();
1060
1061 List<ScanResult> filteredScanResultList = new ArrayList<>();
1062
1063 // Iterate through the list of scan results and filter out APs which are not
1064 // compatible with our device.
1065 for (ScanResult scanResult : scanResults) {
1066 if (scanResult.capabilities.contains(WIFI_SECURITY_PSK)) {
1067 // All devices (today) support RSN-PSK or WPA-PSK
1068 // Add this here because some APs may support both PSK and SAE and the check
1069 // below will filter it out.
1070 filteredScanResultList.add(scanResult);
1071 continue;
1072 }
1073
1074 if ((scanResult.capabilities.contains(WIFI_SECURITY_SUITE_B_192) && !isSuiteBSupported)
1075 || (scanResult.capabilities.contains(WIFI_SECURITY_SAE) && !isSaeSupported)
1076 || (scanResult.capabilities.contains(WIFI_SECURITY_OWE) && !isOweSupported)) {
1077 if (isVerboseLoggingEnabled()) {
1078 Log.v(TAG, "filterScanResultsByCapabilities: Filtering SSID "
1079 + scanResult.SSID + " with capabilities: " + scanResult.capabilities);
1080 }
1081 } else {
1082 // Safe to add
1083 filteredScanResultList.add(scanResult);
1084 }
1085 }
1086
1087 return filteredScanResultList;
1088 }
Jason Monkd52356a2015-01-28 10:40:41 -05001089}