blob: 8ea0f4b6ba610f6a2d32807714a81f9a172dfe27 [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
Quang Luongda49e822019-01-15 14:48:25 -080056import com.android.internal.util.CollectionUtils;
Jason Monkd52356a2015-01-28 10:40:41 -050057import com.android.settingslib.R;
Tony Mantler0edf09b2017-09-28 15:03:37 -070058import com.android.settingslib.core.lifecycle.Lifecycle;
59import com.android.settingslib.core.lifecycle.LifecycleObserver;
60import com.android.settingslib.core.lifecycle.events.OnDestroy;
61import com.android.settingslib.core.lifecycle.events.OnStart;
62import com.android.settingslib.core.lifecycle.events.OnStop;
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -080063import com.android.settingslib.utils.ThreadUtils;
Jason Monkd52356a2015-01-28 10:40:41 -050064
65import java.io.PrintWriter;
66import java.util.ArrayList;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070067import java.util.Collection;
Jason Monkd52356a2015-01-28 10:40:41 -050068import java.util.Collections;
69import java.util.HashMap;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070070import java.util.Iterator;
Jason Monkd52356a2015-01-28 10:40:41 -050071import java.util.List;
Quang Luongac1026e2019-01-02 17:37:01 -080072import java.util.ListIterator;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070073import java.util.Map;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080074import java.util.Set;
Jason Monkd52356a2015-01-28 10:40:41 -050075import java.util.concurrent.atomic.AtomicBoolean;
76
77/**
78 * Tracks saved or available wifi networks and their state.
79 */
Tony Mantler0edf09b2017-09-28 15:03:37 -070080public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -070081 /**
82 * Default maximum age in millis of cached scored networks in
83 * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
84 */
Sundeep Ghuman64bf0902017-09-11 13:00:53 -070085 private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080086
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080087 /** Maximum age of scan results to hold onto while actively scanning. **/
jackqdyulei396e52d2018-09-19 17:28:27 -070088 private static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000;
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080089
Jason Monkd52356a2015-01-28 10:40:41 -050090 private static final String TAG = "WifiTracker";
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -070091 private static final boolean DBG() {
92 return Log.isLoggable(TAG, Log.DEBUG);
93 }
Jason Monkd52356a2015-01-28 10:40:41 -050094
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -080095 private static boolean isVerboseLoggingEnabled() {
96 return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
97 }
98
99 /**
100 * Verbose logging flag set thru developer debugging options and used so as to assist with
101 * in-the-field WiFi connectivity debugging.
102 *
103 * <p>{@link #isVerboseLoggingEnabled()} should be read rather than referencing this value
104 * directly, to ensure adb TAG level verbose settings are respected.
105 */
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700106 public static boolean sVerboseLogging;
Jason Monkd52356a2015-01-28 10:40:41 -0500107
108 // TODO: Allow control of this?
109 // Combo scans can take 5-6s to complete - set to 10s.
110 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
111
112 private final Context mContext;
113 private final WifiManager mWifiManager;
114 private final IntentFilter mFilter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900115 private final ConnectivityManager mConnectivityManager;
116 private final NetworkRequest mNetworkRequest;
Jason Monkd52356a2015-01-28 10:40:41 -0500117 private final AtomicBoolean mConnected = new AtomicBoolean(false);
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800118 private final WifiListenerExecutor mListener;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800119 @VisibleForTesting Handler mWorkHandler;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700120 private HandlerThread mWorkThread;
Jason Monk30d80042015-05-08 16:54:18 -0400121
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700122 private WifiTrackerNetworkCallback mNetworkCallback;
123
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800124 /**
125 * Synchronization lock for managing concurrency between main and worker threads.
126 *
127 * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}.
128 */
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
146 // Does not need to be locked as it only updated on the worker thread, with the exception of
147 // during onStart, which occurs before the receiver is registered on the work handler.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700148 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800149 private boolean mRegistered;
Jason Monkd52356a2015-01-28 10:40:41 -0500150
151 private NetworkInfo mLastNetworkInfo;
152 private WifiInfo mLastInfo;
153
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800154 private final NetworkScoreManager mNetworkScoreManager;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700155 private WifiNetworkScoreCache mScoreCache;
Sundeep Ghumane869d832017-01-25 16:23:43 -0800156 private boolean mNetworkScoringUiEnabled;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700157 private long mMaxSpeedLabelScoreCacheAge;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800158
Hai Shalome3cc0682018-11-08 14:21:58 -0800159 private static final String WIFI_SECURITY_PSK = "PSK";
160 private static final String WIFI_SECURITY_EAP = "EAP";
161 private static final String WIFI_SECURITY_SAE = "SAE";
162 private static final String WIFI_SECURITY_OWE = "OWE";
163 private static final String WIFI_SECURITY_SUITE_B_192 = "SUITE_B_192";
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700164
Jason Monkd52356a2015-01-28 10:40:41 -0500165 @VisibleForTesting
166 Scanner mScanner;
Sundeep Ghuman42058742017-07-21 18:42:10 -0700167
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700168 private static IntentFilter newIntentFilter() {
169 IntentFilter filter = new IntentFilter();
170 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
171 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
172 filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
173 filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
174 filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
175 filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
176 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
177 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
178
179 return filter;
180 }
181
Tony Mantler0edf09b2017-09-28 15:03:37 -0700182 /**
183 * Use the lifecycle constructor below whenever possible
184 */
185 @Deprecated
Jason Monk30d80042015-05-08 16:54:18 -0400186 public WifiTracker(Context context, WifiListener wifiListener,
187 boolean includeSaved, boolean includeScans) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700188 this(context, wifiListener,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900189 context.getSystemService(WifiManager.class),
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800190 context.getSystemService(ConnectivityManager.class),
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700191 context.getSystemService(NetworkScoreManager.class),
Tony Mantler0edf09b2017-09-28 15:03:37 -0700192 newIntentFilter());
193 }
194
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700195 // TODO(sghuman): Clean up includeSaved and includeScans from all constructors and linked
Sundeep Ghuman8d157e32018-01-24 20:32:35 -0800196 // calling apps once IC window is complete
Tony Mantler0edf09b2017-09-28 15:03:37 -0700197 public WifiTracker(Context context, WifiListener wifiListener,
198 @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700199 this(context, wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700200 context.getSystemService(WifiManager.class),
201 context.getSystemService(ConnectivityManager.class),
202 context.getSystemService(NetworkScoreManager.class),
203 newIntentFilter());
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700204
Tony Mantler0edf09b2017-09-28 15:03:37 -0700205 lifecycle.addObserver(this);
Jason Monkd52356a2015-01-28 10:40:41 -0500206 }
207
208 @VisibleForTesting
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700209 WifiTracker(Context context, WifiListener wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700210 WifiManager wifiManager, ConnectivityManager connectivityManager,
211 NetworkScoreManager networkScoreManager,
212 IntentFilter filter) {
Jason Monkd52356a2015-01-28 10:40:41 -0500213 mContext = context;
214 mWifiManager = wifiManager;
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700215 mListener = new WifiListenerExecutor(wifiListener);
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900216 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500217
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800218 // check if verbose logging developer option has been turned on or off
Amin Shaikhbc38add2018-12-26 15:31:45 -0500219 sVerboseLogging = mWifiManager != null && (mWifiManager.getVerboseLoggingLevel() > 0);
Jason Monkd52356a2015-01-28 10:40:41 -0500220
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700221 mFilter = filter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900222
223 mNetworkRequest = new NetworkRequest.Builder()
224 .clearCapabilities()
Jeff Sharkeyc159d522018-03-28 10:54:07 -0600225 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900226 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
227 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800228
229 mNetworkScoreManager = networkScoreManager;
230
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800231 // TODO(sghuman): Remove this and create less hacky solution for testing
Tony Mantler0edf09b2017-09-28 15:03:37 -0700232 final HandlerThread workThread = new HandlerThread(TAG
233 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
234 Process.THREAD_PRIORITY_BACKGROUND);
235 workThread.start();
236 setWorkThread(workThread);
237 }
238
239 /**
240 * Sanity warning: this wipes out mScoreCache, so use with extreme caution
241 * @param workThread substitute Handler thread, for testing purposes only
242 */
243 @VisibleForTesting
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800244 // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in
245 // during construction
Tony Mantler0edf09b2017-09-28 15:03:37 -0700246 void setWorkThread(HandlerThread workThread) {
247 mWorkThread = workThread;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800248 mWorkHandler = new Handler(workThread.getLooper());
Tony Mantler0edf09b2017-09-28 15:03:37 -0700249 mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800250 @Override
251 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800252 if (!mRegistered) return;
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700253
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800254 if (Log.isLoggable(TAG, Log.VERBOSE)) {
255 Log.v(TAG, "Score cache was updated with networks: " + networks);
256 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700257 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800258 }
259 });
Jason Monkd52356a2015-01-28 10:40:41 -0500260 }
261
Tony Mantler0edf09b2017-09-28 15:03:37 -0700262 @Override
263 public void onDestroy() {
264 mWorkThread.quit();
265 }
266
Jason Monkd52356a2015-01-28 10:40:41 -0500267 /**
Jason Monkd52356a2015-01-28 10:40:41 -0500268 * Temporarily stop scanning for wifi networks.
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800269 *
270 * <p>Sets {@link #mStaleScanResults} to true.
Jason Monkd52356a2015-01-28 10:40:41 -0500271 */
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800272 private void pauseScanning() {
Jason Monkd52356a2015-01-28 10:40:41 -0500273 if (mScanner != null) {
274 mScanner.pause();
Jason Monk6572eae2015-02-05 10:34:20 -0500275 mScanner = null;
Jason Monkd52356a2015-01-28 10:40:41 -0500276 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800277 mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500278 }
279
280 /**
281 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800282 *
283 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500284 */
285 public void resumeScanning() {
Jason Monk6572eae2015-02-05 10:34:20 -0500286 if (mScanner == null) {
287 mScanner = new Scanner();
288 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700289
Amin Shaikhbc38add2018-12-26 15:31:45 -0500290 if (isWifiEnabled()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500291 mScanner.resume();
292 }
Jason Monkd52356a2015-01-28 10:40:41 -0500293 }
294
295 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800296 * Start tracking wifi networks and scores.
297 *
298 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500299 * then forceUpdate() must be called to populate getAccessPoints().
300 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700301 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700302 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700303 public void onStart() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800304 // fetch current ScanResults instead of waiting for broadcast of fresh results
305 forceUpdate();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800306
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800307 registerScoreCache();
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700308
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800309 mNetworkScoringUiEnabled =
310 Settings.Global.getInt(
311 mContext.getContentResolver(),
312 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800313
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800314 mMaxSpeedLabelScoreCacheAge =
315 Settings.Global.getLong(
316 mContext.getContentResolver(),
317 Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
318 DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
Sundeep Ghumanf3421742018-02-08 11:18:42 -0800319
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800320 resumeScanning();
321 if (!mRegistered) {
322 mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler);
323 // NetworkCallback objects cannot be reused. http://b/20701525 .
324 mNetworkCallback = new WifiTrackerNetworkCallback();
Erik Kline04ed4302018-04-05 23:28:00 -0700325 mConnectivityManager.registerNetworkCallback(
326 mNetworkRequest, mNetworkCallback, mWorkHandler);
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800327 mRegistered = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500328 }
329 }
330
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800331
332 /**
333 * Synchronously update the list of access points with the latest information.
334 *
335 * <p>Intended to only be invoked within {@link #onStart()}.
336 */
337 @MainThread
jackqdyulei396e52d2018-09-19 17:28:27 -0700338 @VisibleForTesting
339 void forceUpdate() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800340 mLastInfo = mWifiManager.getConnectionInfo();
341 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
342
343 fetchScansAndConfigsAndUpdateAccessPoints();
344 }
345
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800346 private void registerScoreCache() {
347 mNetworkScoreManager.registerNetworkScoreCache(
348 NetworkKey.TYPE_WIFI,
349 mScoreCache,
350 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
351 }
352
353 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
354 if (keys.isEmpty()) return;
355
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700356 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800357 Log.d(TAG, "Requesting scores for Network Keys: " + keys);
358 }
359 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700360 synchronized (mLock) {
361 mRequestedScores.addAll(keys);
362 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800363 }
364
Jason Monkd52356a2015-01-28 10:40:41 -0500365 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800366 * Stop tracking wifi networks and scores.
367 *
Tony Mantler0edf09b2017-09-28 15:03:37 -0700368 * <p>This should always be called when done with a WifiTracker (if onStart was called) to
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700369 * ensure proper cleanup and prevent any further callbacks from occurring.
370 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700371 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700372 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
373 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
Jason Monkd52356a2015-01-28 10:40:41 -0500374 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700375 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700376 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700377 public void onStop() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800378 if (mRegistered) {
379 mContext.unregisterReceiver(mReceiver);
380 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
381 mRegistered = false;
Jason Monkd52356a2015-01-28 10:40:41 -0500382 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800383 unregisterScoreCache();
384 pauseScanning(); // and set mStaleScanResults
385
386 mWorkHandler.removeCallbacksAndMessages(null /* remove all */);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800387 }
388
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700389 private void unregisterScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800390 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800391
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700392 // We do not want to clear the existing scores in the cache, as this method is called during
393 // stop tracking on activity pause. Hence, on resumption we want the ability to show the
394 // last known, potentially stale, scores. However, by clearing requested scores, the scores
395 // will be requested again upon resumption of tracking, and if any changes have occurred
396 // the listeners (UI) will be updated accordingly.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700397 synchronized (mLock) {
398 mRequestedScores.clear();
399 }
Jason Monkd52356a2015-01-28 10:40:41 -0500400 }
401
402 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800403 * Gets the current list of access points.
404 *
405 * <p>This method is can be called on an abitrary thread by clients, but is normally called on
406 * the UI Thread by the rendering App.
Jason Monkd52356a2015-01-28 10:40:41 -0500407 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800408 @AnyThread
Jason Monkd52356a2015-01-28 10:40:41 -0500409 public List<AccessPoint> getAccessPoints() {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800410 synchronized (mLock) {
411 return new ArrayList<>(mInternalAccessPoints);
412 }
Jason Monkd52356a2015-01-28 10:40:41 -0500413 }
414
415 public WifiManager getManager() {
416 return mWifiManager;
417 }
418
419 public boolean isWifiEnabled() {
Amin Shaikhbc38add2018-12-26 15:31:45 -0500420 return mWifiManager != null && mWifiManager.isWifiEnabled();
Jason Monkd52356a2015-01-28 10:40:41 -0500421 }
422
423 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700424 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
425 * is tracking saved networks.
Peter Qiuabea7262017-05-31 10:18:17 -0700426 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
427 * directly.
Jason Monkd52356a2015-01-28 10:40:41 -0500428 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700429 public int getNumSavedNetworks() {
Peter Qiuabea7262017-05-31 10:18:17 -0700430 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
Jason Monkd52356a2015-01-28 10:40:41 -0500431 }
432
433 public boolean isConnected() {
434 return mConnected.get();
435 }
436
437 public void dump(PrintWriter pw) {
438 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400439 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500440 pw.println(" " + accessPoint);
441 }
442 }
443
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800444 private ArrayMap<String, List<ScanResult>> updateScanResultCache(
445 final List<ScanResult> newResults) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800446 // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for
447 // memory efficiency
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700448 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700449 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
450 continue;
451 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700452 mScanResultCache.put(newResult.BSSID, newResult);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700453 }
454
jackqdyulei396e52d2018-09-19 17:28:27 -0700455 // Evict old results in all conditions
456 evictOldScans();
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700457
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800458 ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
459 for (ScanResult result : mScanResultCache.values()) {
460 // Ignore hidden and ad-hoc networks.
461 if (result.SSID == null || result.SSID.length() == 0 ||
462 result.capabilities.contains("[IBSS]")) {
463 continue;
464 }
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800465
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800466 String apKey = AccessPoint.getKey(result);
467 List<ScanResult> resultList;
468 if (scanResultsByApKey.containsKey(apKey)) {
469 resultList = scanResultsByApKey.get(apKey);
470 } else {
471 resultList = new ArrayList<>();
472 scanResultsByApKey.put(apKey, resultList);
473 }
474
475 resultList.add(result);
476 }
477
478 return scanResultsByApKey;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700479 }
480
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800481 /**
482 * Remove old scan results from the cache.
483 *
484 * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when
485 * {@link #mStaleScanResults} is false.
486 */
487 private void evictOldScans() {
488 long nowMs = SystemClock.elapsedRealtime();
489 for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
490 ScanResult result = iter.next();
491 // result timestamp is in microseconds
492 if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) {
493 iter.remove();
494 }
495 }
496 }
497
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700498 private WifiConfiguration getWifiConfigurationForNetworkId(
499 int networkId, final List<WifiConfiguration> configs) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700500 if (configs != null) {
501 for (WifiConfiguration config : configs) {
502 if (mLastInfo != null && networkId == config.networkId &&
503 !(config.selfAdded && config.numAssociation == 0)) {
504 return config;
505 }
506 }
507 }
508 return null;
509 }
510
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700511 /**
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800512 * Retrieves latest scan results and wifi configs, then calls
513 * {@link #updateAccessPoints(List, List)}.
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700514 */
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800515 private void fetchScansAndConfigsAndUpdateAccessPoints() {
Hai Shalome3cc0682018-11-08 14:21:58 -0800516 List<ScanResult> newScanResults = mWifiManager.getScanResults();
517
518 // Filter all unsupported networks from the scan result list
519 final List<ScanResult> filteredScanResults =
520 filterScanResultsByCapabilities(newScanResults);
521
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800522 if (isVerboseLoggingEnabled()) {
Hai Shalome3cc0682018-11-08 14:21:58 -0800523 Log.i(TAG, "Fetched scan results: " + filteredScanResults);
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700524 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700525
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800526 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
Hai Shalome3cc0682018-11-08 14:21:58 -0800527 updateAccessPoints(filteredScanResults, configs);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700528 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700529
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800530 /** Update the internal list of access points. */
531 private void updateAccessPoints(final List<ScanResult> newScanResults,
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700532 List<WifiConfiguration> configs) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800533
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800534 // Map configs and scan results necessary to make AccessPoints
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800535 final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
Jason Monkd52356a2015-01-28 10:40:41 -0500536 if (configs != null) {
Jason Monkd52356a2015-01-28 10:40:41 -0500537 for (WifiConfiguration config : configs) {
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800538 configsByKey.put(AccessPoint.getKey(config), config);
Jason Monkd52356a2015-01-28 10:40:41 -0500539 }
540 }
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800541 ArrayMap<String, List<ScanResult>> scanResultsByApKey =
542 updateScanResultCache(newScanResults);
543
544 WifiConfiguration connectionConfig = null;
545 if (mLastInfo != null) {
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800546 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs);
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800547 }
548
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800549 // Rather than dropping and reacquiring the lock multiple times in this method, we lock
550 // once for efficiency of lock acquisition time and readability
551 synchronized (mLock) {
552 // Swap the current access points into a cached list for maintaining AP listeners
553 List<AccessPoint> cachedAccessPoints;
554 cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500555
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800556 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500557
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800558 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800559
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800560 for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) {
561 for (ScanResult result : entry.getValue()) {
562 NetworkKey key = NetworkKey.createFromScanResult(result);
563 if (key != null && !mRequestedScores.contains(key)) {
564 scoresToRequest.add(key);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800565 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700566 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700567
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800568 AccessPoint accessPoint =
569 getCachedOrCreate(entry.getValue(), cachedAccessPoints);
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800570
571 // Update the matching config if there is one, to populate saved network info
572 accessPoint.update(configsByKey.get(entry.getKey()));
573
574 accessPoints.add(accessPoint);
575 }
576
Quang Luong7054e922018-12-07 16:44:18 -0800577 List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values());
578
Quang Luongda49e822019-01-15 14:48:25 -0800579 // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN.
580 accessPoints.addAll(updatePasspointAccessPoints(
581 mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints));
Quang Luong7054e922018-12-07 16:44:18 -0800582
Quang Luongda49e822019-01-15 14:48:25 -0800583 // Add OSU Provider AccessPoints
584 accessPoints.addAll(updateOsuAccessPoints(
585 mWifiManager.getMatchingOsuProviders(cachedScanResults), cachedAccessPoints));
Quang Luong7054e922018-12-07 16:44:18 -0800586
Quang Luongda49e822019-01-15 14:48:25 -0800587 if (mLastInfo != null && mLastNetworkInfo != null) {
588 for (AccessPoint ap : accessPoints) {
589 ap.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Quang Luong7054e922018-12-07 16:44:18 -0800590 }
591 }
592
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800593 // If there were no scan results, create an AP for the currently connected network (if
594 // it exists).
Sundeep Ghuman06ec3be2018-02-20 19:51:15 -0800595 if (accessPoints.isEmpty() && connectionConfig != null) {
596 AccessPoint activeAp = new AccessPoint(mContext, connectionConfig);
597 activeAp.update(connectionConfig, mLastInfo, mLastNetworkInfo);
598 accessPoints.add(activeAp);
599 scoresToRequest.add(NetworkKey.createFromWifiInfo(mLastInfo));
600 }
601
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800602 requestScoresForNetworkKeys(scoresToRequest);
603 for (AccessPoint ap : accessPoints) {
604 ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
605 }
606
607 // Pre-sort accessPoints to speed preference insertion
608 Collections.sort(accessPoints);
609
610 // Log accesspoints that are being removed
611 if (DBG()) {
612 Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
613 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
614 if (prevAccessPoint.getSsid() == null)
615 continue;
616 String prevSsid = prevAccessPoint.getSsidStr();
617 boolean found = false;
618 for (AccessPoint newAccessPoint : accessPoints) {
619 if (newAccessPoint.getSsidStr() != null && newAccessPoint.getSsidStr()
620 .equals(prevSsid)) {
621 found = true;
622 break;
623 }
624 }
625 if (!found)
626 Log.d(TAG, "Did not find " + prevSsid + " in this scan");
627 }
628 Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
629 }
630
631 mInternalAccessPoints.clear();
632 mInternalAccessPoints.addAll(accessPoints);
633 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700634
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800635 conditionallyNotifyListeners();
Jason Monkd52356a2015-01-28 10:40:41 -0500636 }
637
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700638 @VisibleForTesting
Quang Luongda49e822019-01-15 14:48:25 -0800639 List<AccessPoint> updatePasspointAccessPoints(
640 List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans,
641 List<AccessPoint> accessPointCache) {
642 List<AccessPoint> accessPoints = new ArrayList<>();
643
644 Set<String> seenFQDNs = new ArraySet<>();
645 for (Pair<WifiConfiguration,
646 Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) {
647 WifiConfiguration config = pairing.first;
648 if (seenFQDNs.add(config.FQDN)) {
649 List<ScanResult> apScanResults = new ArrayList<>();
650
651 List<ScanResult> homeScans =
652 pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
653 List<ScanResult> roamingScans =
654 pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
655
656 // TODO(b/118705403): Differentiate home network vs roaming network for summary info
657 if (!CollectionUtils.isEmpty(homeScans)) {
658 apScanResults.addAll(homeScans);
659 } else if (!CollectionUtils.isEmpty(roamingScans)) {
660 apScanResults.addAll(roamingScans);
661 }
662
663 int bestRssi = Integer.MIN_VALUE;
664 for (ScanResult result : apScanResults) {
665 if (result.level >= bestRssi) {
666 bestRssi = result.level;
667 config.SSID = AccessPoint.convertToQuotedString(result.SSID);
668 }
669 }
670
671 AccessPoint accessPoint =
672 getCachedOrCreatePasspoint(apScanResults, accessPointCache, config);
673 accessPoints.add(accessPoint);
674 }
675 }
676 return accessPoints;
677 }
678
679 @VisibleForTesting
680 List<AccessPoint> updateOsuAccessPoints(
681 Map<OsuProvider, List<ScanResult>> providersAndScans,
682 List<AccessPoint> accessPointCache) {
683 List<AccessPoint> accessPoints = new ArrayList<>();
684
685 Set<OsuProvider> alreadyProvisioned = mWifiManager
686 .getMatchingPasspointConfigsForOsuProviders(
687 providersAndScans.keySet()).keySet();
688 for (OsuProvider provider : providersAndScans.keySet()) {
689 if (!alreadyProvisioned.contains(provider)) {
690 AccessPoint accessPointOsu =
691 getCachedOrCreateOsu(providersAndScans.get(provider),
692 accessPointCache, provider);
693 accessPoints.add(accessPointOsu);
694 }
695 }
696 return accessPoints;
697 }
698
699 private AccessPoint getCachedOrCreate(
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800700 List<ScanResult> scanResults,
701 List<AccessPoint> cache) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800702 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0)));
703 if (accessPoint == null) {
704 accessPoint = new AccessPoint(mContext, scanResults);
705 } else {
706 accessPoint.setScanResults(scanResults);
Jason Monkd52356a2015-01-28 10:40:41 -0500707 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800708 return accessPoint;
709 }
710
711 private AccessPoint getCachedOrCreatePasspoint(
712 List<ScanResult> scanResults,
713 List<AccessPoint> cache,
714 WifiConfiguration config) {
715 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config));
716 if (accessPoint == null) {
717 accessPoint = new AccessPoint(mContext, config);
718 }
719 accessPoint.setScanResults(scanResults);
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700720 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500721 }
722
Quang Luongac1026e2019-01-02 17:37:01 -0800723 private AccessPoint getCachedOrCreateOsu(
724 List<ScanResult> scanResults,
725 List<AccessPoint> cache,
726 OsuProvider provider) {
Quang Luong1a8fece2019-01-11 14:58:14 -0800727 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider));
728 if (accessPoint == null) {
729 accessPoint = new AccessPoint(mContext, provider);
730 }
731 accessPoint.setScanResults(scanResults);
732 return accessPoint;
733 }
734
735 private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) {
Quang Luongac1026e2019-01-02 17:37:01 -0800736 ListIterator<AccessPoint> lit = cache.listIterator();
737 while (lit.hasNext()) {
Quang Luong550f7d42019-01-09 17:07:19 -0800738 AccessPoint currentAccessPoint = lit.next();
Quang Luong1a8fece2019-01-11 14:58:14 -0800739 if (currentAccessPoint.getKey().equals(key)) {
Quang Luongac1026e2019-01-02 17:37:01 -0800740 lit.remove();
Quang Luong1a8fece2019-01-11 14:58:14 -0800741 return currentAccessPoint;
Quang Luongac1026e2019-01-02 17:37:01 -0800742 }
743 }
Quang Luong1a8fece2019-01-11 14:58:14 -0800744 return null;
Quang Luongac1026e2019-01-02 17:37:01 -0800745 }
746
Jason Monkd52356a2015-01-28 10:40:41 -0500747 private void updateNetworkInfo(NetworkInfo networkInfo) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800748
749 /* Sticky broadcasts can call this when wifi is disabled */
Amin Shaikhbc38add2018-12-26 15:31:45 -0500750 if (!isWifiEnabled()) {
Sundeep Ghumane8013092017-06-21 22:35:30 -0700751 clearAccessPointsAndConditionallyUpdate();
Jason Monke04ae8a2015-06-03 14:06:34 -0400752 return;
753 }
Jason Monkd52356a2015-01-28 10:40:41 -0500754
Jason Monkd52356a2015-01-28 10:40:41 -0500755 if (networkInfo != null) {
756 mLastNetworkInfo = networkInfo;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700757 if (DBG()) {
758 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo);
759 }
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800760
761 if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) {
762 mListener.onConnectedChanged();
763 }
Jason Monkd52356a2015-01-28 10:40:41 -0500764 }
765
Mitchell Wills5a42db22015-08-03 09:46:08 -0700766 WifiConfiguration connectionConfig = null;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700767
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900768 mLastInfo = mWifiManager.getConnectionInfo();
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700769 if (DBG()) {
770 Log.d(TAG, "mLastInfo set as: " + mLastInfo);
771 }
Mitchell Wills5a42db22015-08-03 09:46:08 -0700772 if (mLastInfo != null) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700773 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
774 mWifiManager.getConfiguredNetworks());
Mitchell Wills5a42db22015-08-03 09:46:08 -0700775 }
776
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700777 boolean updated = false;
778 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700779
780 synchronized (mLock) {
781 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
782 AccessPoint ap = mInternalAccessPoints.get(i);
783 boolean previouslyConnected = ap.isActive();
784 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
785 updated = true;
786 if (previouslyConnected != ap.isActive()) reorder = true;
787 }
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700788 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700789 reorder = true;
790 updated = true;
791 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800792 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700793
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800794 if (reorder) {
795 Collections.sort(mInternalAccessPoints);
796 }
797 if (updated) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800798 conditionallyNotifyListeners();
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800799 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800800 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800801 }
802
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800803 /**
804 * Clears the access point list and conditionally invokes
805 * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already
806 * empty).
807 */
Sundeep Ghumane8013092017-06-21 22:35:30 -0700808 private void clearAccessPointsAndConditionallyUpdate() {
809 synchronized (mLock) {
810 if (!mInternalAccessPoints.isEmpty()) {
811 mInternalAccessPoints.clear();
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -0800812 conditionallyNotifyListeners();
Sundeep Ghumane8013092017-06-21 22:35:30 -0700813 }
814 }
815 }
816
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800817 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700818 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800819 *
820 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700821 *
822 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800823 */
824 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700825 synchronized (mLock) {
826 boolean updated = false;
827 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700828 if (mInternalAccessPoints.get(i).update(
829 mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700830 updated = true;
831 }
Jason Monkd52356a2015-01-28 10:40:41 -0500832 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700833 if (updated) {
834 Collections.sort(mInternalAccessPoints);
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800835 conditionallyNotifyListeners();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700836 }
Jason Monkd52356a2015-01-28 10:40:41 -0500837 }
838 }
839
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800840 /**
841 * Receiver for handling broadcasts.
842 *
843 * This receiver is registered on the WorkHandler.
844 */
Jason Monkd52356a2015-01-28 10:40:41 -0500845 @VisibleForTesting
846 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
847 @Override
848 public void onReceive(Context context, Intent intent) {
849 String action = intent.getAction();
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700850
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800851 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
852 updateWifiState(
853 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
854 WifiManager.WIFI_STATE_UNKNOWN));
855 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
856 mStaleScanResults = false;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800857
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800858 fetchScansAndConfigsAndUpdateAccessPoints();
859 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
860 || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
861 fetchScansAndConfigsAndUpdateAccessPoints();
862 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
863 // TODO(sghuman): Refactor these methods so they cannot result in duplicate
864 // onAccessPointsChanged updates being called from this intent.
865 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
866 updateNetworkInfo(info);
867 fetchScansAndConfigsAndUpdateAccessPoints();
868 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
869 NetworkInfo info =
870 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
871 updateNetworkInfo(info);
Jason Monkd52356a2015-01-28 10:40:41 -0500872 }
873 }
874 };
875
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800876 /**
877 * Handles updates to WifiState.
878 *
879 * <p>If Wifi is not enabled in the enabled state, {@link #mStaleScanResults} will be set to
880 * true.
881 */
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800882 private void updateWifiState(int state) {
883 if (state == WifiManager.WIFI_STATE_ENABLED) {
884 if (mScanner != null) {
885 // We only need to resume if mScanner isn't null because
886 // that means we want to be scanning.
887 mScanner.resume();
888 }
889 } else {
890 clearAccessPointsAndConditionallyUpdate();
891 mLastInfo = null;
892 mLastNetworkInfo = null;
893 if (mScanner != null) {
894 mScanner.pause();
895 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800896 mStaleScanResults = true;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800897 }
898 mListener.onWifiStateChanged(state);
899 }
900
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900901 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
902 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
903 if (network.equals(mWifiManager.getCurrentNetwork())) {
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800904 // TODO(sghuman): Investigate whether this comment still holds true and if it makes
905 // more sense fetch the latest network info here:
906
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900907 // We don't send a NetworkInfo object along with this message, because even if we
908 // fetch one from ConnectivityManager, it might be older than the most recent
909 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
Erik Kline04ed4302018-04-05 23:28:00 -0700910 updateNetworkInfo(null);
Jason Monk30d80042015-05-08 16:54:18 -0400911 }
912 }
913 }
914
Jason Monkd52356a2015-01-28 10:40:41 -0500915 @VisibleForTesting
916 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400917 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500918
Jason Monkd52356a2015-01-28 10:40:41 -0500919 private int mRetry = 0;
920
921 void resume() {
Jason Monk6572eae2015-02-05 10:34:20 -0500922 if (!hasMessages(MSG_SCAN)) {
923 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500924 }
925 }
926
Jason Monkd52356a2015-01-28 10:40:41 -0500927 void pause() {
928 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500929 removeMessages(MSG_SCAN);
930 }
931
932 @VisibleForTesting
933 boolean isScanning() {
934 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500935 }
936
937 @Override
938 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500939 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500940 if (mWifiManager.startScan()) {
941 mRetry = 0;
942 } else if (++mRetry >= 3) {
943 mRetry = 0;
944 if (mContext != null) {
945 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
946 }
947 return;
948 }
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700949 sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS);
Jason Monkd52356a2015-01-28 10:40:41 -0500950 }
951 }
952
953 /** A restricted multimap for use in constructAccessPoints */
954 private static class Multimap<K,V> {
955 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
956 /** retrieve a non-null list of values with key K */
957 List<V> getAll(K key) {
958 List<V> values = store.get(key);
959 return values != null ? values : Collections.<V>emptyList();
960 }
961
962 void put(K key, V val) {
963 List<V> curVals = store.get(key);
964 if (curVals == null) {
965 curVals = new ArrayList<V>(3);
966 store.put(key, curVals);
967 }
968 curVals.add(val);
969 }
970 }
971
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800972 /**
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800973 * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800974 *
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800975 * <p>Also logs all callbacks invocations when verbose logging is enabled.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800976 */
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700977 @VisibleForTesting class WifiListenerExecutor implements WifiListener {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800978
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800979 private final WifiListener mDelegatee;
980
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800981 public WifiListenerExecutor(WifiListener listener) {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800982 mDelegatee = listener;
983 }
984
985 @Override
986 public void onWifiStateChanged(int state) {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700987 runAndLog(() -> mDelegatee.onWifiStateChanged(state),
988 String.format("Invoking onWifiStateChanged callback with state %d", state));
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800989 }
990
991 @Override
992 public void onConnectedChanged() {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700993 runAndLog(mDelegatee::onConnectedChanged, "Invoking onConnectedChanged callback");
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800994 }
995
996 @Override
997 public void onAccessPointsChanged() {
Sundeep Ghuman93d591b2018-03-19 14:21:56 -0700998 runAndLog(mDelegatee::onAccessPointsChanged, "Invoking onAccessPointsChanged callback");
999 }
1000
1001 private void runAndLog(Runnable r, String verboseLog) {
1002 ThreadUtils.postOnMainThread(() -> {
1003 if (mRegistered) {
1004 if (isVerboseLoggingEnabled()) {
1005 Log.i(TAG, verboseLog);
1006 }
1007 r.run();
1008 }
1009 });
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -08001010 }
1011 }
1012
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -08001013 /**
1014 * WifiListener interface that defines callbacks indicating state changes in WifiTracker.
1015 *
1016 * <p>All callbacks are invoked on the MainThread.
1017 */
Jason Monkd52356a2015-01-28 10:40:41 -05001018 public interface WifiListener {
1019 /**
1020 * Called when the state of Wifi has changed, the state will be one of
1021 * the following.
1022 *
1023 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
1024 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
1025 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
1026 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
1027 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
1028 * <p>
1029 *
1030 * @param state The new state of wifi.
1031 */
1032 void onWifiStateChanged(int state);
1033
1034 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001035 * Called when the connection state of wifi has changed and
1036 * {@link WifiTracker#isConnected()} should be called to get the updated state.
Jason Monkd52356a2015-01-28 10:40:41 -05001037 */
1038 void onConnectedChanged();
1039
1040 /**
1041 * Called to indicate the list of AccessPoints has been updated and
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001042 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
Jason Monkd52356a2015-01-28 10:40:41 -05001043 */
1044 void onAccessPointsChanged();
1045 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001046
1047 /**
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -08001048 * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001049 * is false.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001050 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001051 private void conditionallyNotifyListeners() {
1052 if (mStaleScanResults) {
1053 return;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001054 }
1055
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -08001056 mListener.onAccessPointsChanged();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001057 }
Hai Shalome3cc0682018-11-08 14:21:58 -08001058
1059 /**
1060 * Filters unsupported networks from scan results. New WPA3 networks and OWE networks
1061 * may not be compatible with the device HW/SW.
1062 * @param scanResults List of scan results
1063 * @return List of filtered scan results based on local device capabilities
1064 */
1065 private List<ScanResult> filterScanResultsByCapabilities(List<ScanResult> scanResults) {
1066 if (scanResults == null) {
1067 return null;
1068 }
1069
1070 // Get and cache advanced capabilities
1071 final boolean isOweSupported = mWifiManager.isOweSupported();
1072 final boolean isSaeSupported = mWifiManager.isWpa3SaeSupported();
1073 final boolean isSuiteBSupported = mWifiManager.isWpa3SuiteBSupported();
1074
1075 List<ScanResult> filteredScanResultList = new ArrayList<>();
1076
1077 // Iterate through the list of scan results and filter out APs which are not
1078 // compatible with our device.
1079 for (ScanResult scanResult : scanResults) {
1080 if (scanResult.capabilities.contains(WIFI_SECURITY_PSK)) {
1081 // All devices (today) support RSN-PSK or WPA-PSK
1082 // Add this here because some APs may support both PSK and SAE and the check
1083 // below will filter it out.
1084 filteredScanResultList.add(scanResult);
1085 continue;
1086 }
1087
1088 if ((scanResult.capabilities.contains(WIFI_SECURITY_SUITE_B_192) && !isSuiteBSupported)
1089 || (scanResult.capabilities.contains(WIFI_SECURITY_SAE) && !isSaeSupported)
1090 || (scanResult.capabilities.contains(WIFI_SECURITY_OWE) && !isOweSupported)) {
1091 if (isVerboseLoggingEnabled()) {
1092 Log.v(TAG, "filterScanResultsByCapabilities: Filtering SSID "
1093 + scanResult.SSID + " with capabilities: " + scanResult.capabilities);
1094 }
1095 } else {
1096 // Safe to add
1097 filteredScanResultList.add(scanResult);
1098 }
1099 }
1100
1101 return filteredScanResultList;
1102 }
Jason Monkd52356a2015-01-28 10:40:41 -05001103}