blob: ae544dd6dbe8f7301f254d30752934a1b26b4633 [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;
Jason Monkd52356a2015-01-28 10:40:41 -050038import android.os.Handler;
Tony Mantler0edf09b2017-09-28 15:03:37 -070039import android.os.HandlerThread;
Jason Monk30d80042015-05-08 16:54:18 -040040import android.os.Looper;
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 Ghumance6bab82017-04-19 16:21:46 -070045import android.support.annotation.GuardedBy;
Tony Mantler0edf09b2017-09-28 15:03:37 -070046import android.support.annotation.NonNull;
47import android.support.annotation.VisibleForTesting;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -070048import android.text.format.DateUtils;
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080049import android.util.ArrayMap;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080050import android.util.ArraySet;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070051import android.util.Log;
Jason Monkd52356a2015-01-28 10:40:41 -050052import android.widget.Toast;
53
Jason Monkd52356a2015-01-28 10:40:41 -050054import com.android.settingslib.R;
Tony Mantler0edf09b2017-09-28 15:03:37 -070055import com.android.settingslib.core.lifecycle.Lifecycle;
56import com.android.settingslib.core.lifecycle.LifecycleObserver;
57import com.android.settingslib.core.lifecycle.events.OnDestroy;
58import com.android.settingslib.core.lifecycle.events.OnStart;
59import com.android.settingslib.core.lifecycle.events.OnStop;
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -080060import com.android.settingslib.utils.ThreadUtils;
Jason Monkd52356a2015-01-28 10:40:41 -050061
62import java.io.PrintWriter;
63import java.util.ArrayList;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070064import java.util.Collection;
Jason Monkd52356a2015-01-28 10:40:41 -050065import java.util.Collections;
66import java.util.HashMap;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070067import java.util.Iterator;
Jason Monkd52356a2015-01-28 10:40:41 -050068import java.util.List;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070069import java.util.Map;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080070import java.util.Set;
Jason Monkd52356a2015-01-28 10:40:41 -050071import java.util.concurrent.atomic.AtomicBoolean;
72
73/**
74 * Tracks saved or available wifi networks and their state.
75 */
Tony Mantler0edf09b2017-09-28 15:03:37 -070076public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -070077 /**
78 * Default maximum age in millis of cached scored networks in
79 * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
80 */
Sundeep Ghuman64bf0902017-09-11 13:00:53 -070081 private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080082
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080083 /** Maximum age of scan results to hold onto while actively scanning. **/
84 private static final long MAX_SCAN_RESULT_AGE_MILLIS = 25000;
85
Jason Monkd52356a2015-01-28 10:40:41 -050086 private static final String TAG = "WifiTracker";
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -070087 private static final boolean DBG() {
88 return Log.isLoggable(TAG, Log.DEBUG);
89 }
Jason Monkd52356a2015-01-28 10:40:41 -050090
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -080091 private static boolean isVerboseLoggingEnabled() {
92 return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
93 }
94
95 /**
96 * Verbose logging flag set thru developer debugging options and used so as to assist with
97 * in-the-field WiFi connectivity debugging.
98 *
99 * <p>{@link #isVerboseLoggingEnabled()} should be read rather than referencing this value
100 * directly, to ensure adb TAG level verbose settings are respected.
101 */
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700102 public static boolean sVerboseLogging;
Jason Monkd52356a2015-01-28 10:40:41 -0500103
104 // TODO: Allow control of this?
105 // Combo scans can take 5-6s to complete - set to 10s.
106 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700107 private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
Jason Monkd52356a2015-01-28 10:40:41 -0500108
109 private final Context mContext;
110 private final WifiManager mWifiManager;
111 private final IntentFilter mFilter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900112 private final ConnectivityManager mConnectivityManager;
113 private final NetworkRequest mNetworkRequest;
Jason Monkd52356a2015-01-28 10:40:41 -0500114 private final AtomicBoolean mConnected = new AtomicBoolean(false);
115 private final WifiListener mListener;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700116 @VisibleForTesting WorkHandler mWorkHandler;
117 private HandlerThread mWorkThread;
Jason Monk30d80042015-05-08 16:54:18 -0400118
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700119 private WifiTrackerNetworkCallback mNetworkCallback;
120
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700121 @GuardedBy("mLock")
Jason Monkd52356a2015-01-28 10:40:41 -0500122 private boolean mRegistered;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700123
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800124 /** The list of AccessPoints, aggregated visible ScanResults with metadata. */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700125 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700126 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
127
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700128 /**
129 * Synchronization lock for managing concurrency between main and worker threads.
130 *
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800131 * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700132 */
133 private final Object mLock = new Object();
134
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700135 private final HashMap<String, Integer> mSeenBssids = new HashMap<>();
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800136
137 // TODO(sghuman): Change this to be keyed on AccessPoint.getKey
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700138 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500139
140 private NetworkInfo mLastNetworkInfo;
141 private WifiInfo mLastInfo;
142
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800143 private final NetworkScoreManager mNetworkScoreManager;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700144 private WifiNetworkScoreCache mScoreCache;
Sundeep Ghumane869d832017-01-25 16:23:43 -0800145 private boolean mNetworkScoringUiEnabled;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700146 private long mMaxSpeedLabelScoreCacheAge;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800147
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700148 @GuardedBy("mLock")
149 private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
150
Jason Monkd52356a2015-01-28 10:40:41 -0500151 @VisibleForTesting
152 Scanner mScanner;
Sundeep Ghuman42058742017-07-21 18:42:10 -0700153
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800154 /**
155 * Tracks whether fresh scan results have been received since scanning start.
156 *
157 * <p>If this variable is false, we will not evict the scan result cache or invoke callbacks
158 * so that we do not update the UI with stale data / clear out existing UI elements prematurely.
159 */
Sundeep Ghuman42058742017-07-21 18:42:10 -0700160 @GuardedBy("mLock")
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700161 private boolean mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500162
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700163 private static IntentFilter newIntentFilter() {
164 IntentFilter filter = new IntentFilter();
165 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
166 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
167 filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
168 filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
169 filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
170 filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
171 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
172 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
173
174 return filter;
175 }
176
Tony Mantler0edf09b2017-09-28 15:03:37 -0700177 /**
178 * Use the lifecycle constructor below whenever possible
179 */
180 @Deprecated
Jason Monk30d80042015-05-08 16:54:18 -0400181 public WifiTracker(Context context, WifiListener wifiListener,
182 boolean includeSaved, boolean includeScans) {
Sundeep Ghuman8d157e32018-01-24 20:32:35 -0800183 this(context, wifiListener,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900184 context.getSystemService(WifiManager.class),
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800185 context.getSystemService(ConnectivityManager.class),
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700186 context.getSystemService(NetworkScoreManager.class),
Tony Mantler0edf09b2017-09-28 15:03:37 -0700187 newIntentFilter());
188 }
189
Sundeep Ghuman8d157e32018-01-24 20:32:35 -0800190 // TODO(Sghuman): Clean up includeSaved and includeScans from all constructors and linked
191 // calling apps once IC window is complete
Tony Mantler0edf09b2017-09-28 15:03:37 -0700192 public WifiTracker(Context context, WifiListener wifiListener,
193 @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
Sundeep Ghuman8d157e32018-01-24 20:32:35 -0800194 this(context, wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700195 context.getSystemService(WifiManager.class),
196 context.getSystemService(ConnectivityManager.class),
197 context.getSystemService(NetworkScoreManager.class),
198 newIntentFilter());
199 lifecycle.addObserver(this);
Jason Monkd52356a2015-01-28 10:40:41 -0500200 }
201
202 @VisibleForTesting
Tony Mantler0edf09b2017-09-28 15:03:37 -0700203 WifiTracker(Context context, WifiListener wifiListener,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700204 WifiManager wifiManager, ConnectivityManager connectivityManager,
205 NetworkScoreManager networkScoreManager,
206 IntentFilter filter) {
Jason Monkd52356a2015-01-28 10:40:41 -0500207 mContext = context;
208 mWifiManager = wifiManager;
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800209 mListener = new WifiListenerWrapper(wifiListener);
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900210 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500211
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800212 // check if verbose logging developer option has been turned on or off
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700213 sVerboseLogging = (mWifiManager.getVerboseLoggingLevel() > 0);
Jason Monkd52356a2015-01-28 10:40:41 -0500214
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700215 mFilter = filter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900216
217 mNetworkRequest = new NetworkRequest.Builder()
218 .clearCapabilities()
219 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
220 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800221
222 mNetworkScoreManager = networkScoreManager;
223
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800224 // TODO(sghuman): Remove this and create less hacky solution for testing
Tony Mantler0edf09b2017-09-28 15:03:37 -0700225 final HandlerThread workThread = new HandlerThread(TAG
226 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
227 Process.THREAD_PRIORITY_BACKGROUND);
228 workThread.start();
229 setWorkThread(workThread);
230 }
231
232 /**
233 * Sanity warning: this wipes out mScoreCache, so use with extreme caution
234 * @param workThread substitute Handler thread, for testing purposes only
235 */
236 @VisibleForTesting
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800237 // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in
238 // during construction
Tony Mantler0edf09b2017-09-28 15:03:37 -0700239 void setWorkThread(HandlerThread workThread) {
240 mWorkThread = workThread;
241 mWorkHandler = new WorkHandler(workThread.getLooper());
242 mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800243 @Override
244 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700245 synchronized (mLock) {
246 if (!mRegistered) return;
247 }
248
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800249 if (Log.isLoggable(TAG, Log.VERBOSE)) {
250 Log.v(TAG, "Score cache was updated with networks: " + networks);
251 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700252 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800253 }
254 });
Jason Monkd52356a2015-01-28 10:40:41 -0500255 }
256
Tony Mantler0edf09b2017-09-28 15:03:37 -0700257 @Override
258 public void onDestroy() {
259 mWorkThread.quit();
260 }
261
Sundeep Ghuman42058742017-07-21 18:42:10 -0700262 /** Synchronously update the list of access points with the latest information. */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700263 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500264 public void forceUpdate() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700265 synchronized (mLock) {
266 mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
267 mLastInfo = mWifiManager.getConnectionInfo();
268 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700269
270 final List<ScanResult> newScanResults = mWifiManager.getScanResults();
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800271 if (isVerboseLoggingEnabled()) {
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700272 Log.i(TAG, "Fetched scan results: " + newScanResults);
273 }
274
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700275 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
Sundeep Ghuman42058742017-07-21 18:42:10 -0700276 mInternalAccessPoints.clear();
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700277 updateAccessPointsLocked(newScanResults, configs);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700278 }
Jason Monkd52356a2015-01-28 10:40:41 -0500279 }
280
281 /**
Jason Monkd52356a2015-01-28 10:40:41 -0500282 * Temporarily stop scanning for wifi networks.
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800283 *
284 * <p>Sets {@link #mStaleScanResults} to true.
Jason Monkd52356a2015-01-28 10:40:41 -0500285 */
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800286 private void pauseScanning() {
Jason Monkd52356a2015-01-28 10:40:41 -0500287 if (mScanner != null) {
288 mScanner.pause();
Jason Monk6572eae2015-02-05 10:34:20 -0500289 mScanner = null;
Jason Monkd52356a2015-01-28 10:40:41 -0500290 }
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800291 synchronized (mLock) {
292 mStaleScanResults = true;
293 }
Jason Monkd52356a2015-01-28 10:40:41 -0500294 }
295
296 /**
297 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800298 *
299 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500300 */
301 public void resumeScanning() {
Jason Monk6572eae2015-02-05 10:34:20 -0500302 if (mScanner == null) {
303 mScanner = new Scanner();
304 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700305
Jason Monkdbd05a92015-07-06 12:55:48 -0400306 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
Jason Monkd52356a2015-01-28 10:40:41 -0500307 if (mWifiManager.isWifiEnabled()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500308 mScanner.resume();
309 }
Jason Monkd52356a2015-01-28 10:40:41 -0500310 }
311
312 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800313 * Start tracking wifi networks and scores.
314 *
315 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500316 * then forceUpdate() must be called to populate getAccessPoints().
317 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700318 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700319 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700320 public void onStart() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700321 synchronized (mLock) {
322 registerScoreCache();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800323
Sundeep Ghuman64bf0902017-09-11 13:00:53 -0700324 mNetworkScoringUiEnabled =
325 Settings.Global.getInt(
326 mContext.getContentResolver(),
327 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700328
Sundeep Ghuman64bf0902017-09-11 13:00:53 -0700329 mMaxSpeedLabelScoreCacheAge =
330 Settings.Global.getLong(
331 mContext.getContentResolver(),
332 Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
333 DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800334
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700335 resumeScanning();
336 if (!mRegistered) {
337 mContext.registerReceiver(mReceiver, mFilter);
338 // NetworkCallback objects cannot be reused. http://b/20701525 .
339 mNetworkCallback = new WifiTrackerNetworkCallback();
340 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
341 mRegistered = true;
342 }
Jason Monkd52356a2015-01-28 10:40:41 -0500343 }
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 Ghumance6bab82017-04-19 16:21:46 -0700378 synchronized (mLock) {
379 if (mRegistered) {
380 mContext.unregisterReceiver(mReceiver);
381 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
382 mRegistered = false;
383 }
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700384 unregisterScoreCache();
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800385 pauseScanning(); // and set mStaleScanResults
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700386
387 mWorkHandler.removePendingMessages();
Jason Monkd52356a2015-01-28 10:40:41 -0500388 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800389 }
390
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700391 private void unregisterScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800392 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800393
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700394 // We do not want to clear the existing scores in the cache, as this method is called during
395 // stop tracking on activity pause. Hence, on resumption we want the ability to show the
396 // last known, potentially stale, scores. However, by clearing requested scores, the scores
397 // will be requested again upon resumption of tracking, and if any changes have occurred
398 // the listeners (UI) will be updated accordingly.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700399 synchronized (mLock) {
400 mRequestedScores.clear();
401 }
Jason Monkd52356a2015-01-28 10:40:41 -0500402 }
403
404 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800405 * Gets the current list of access points.
406 *
407 * <p>This method is can be called on an abitrary thread by clients, but is normally called on
408 * the UI Thread by the rendering App.
Jason Monkd52356a2015-01-28 10:40:41 -0500409 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800410 @AnyThread
Jason Monkd52356a2015-01-28 10:40:41 -0500411 public List<AccessPoint> getAccessPoints() {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800412 // TODO(sghuman): Investigate how to eliminate or reduce the need for locking now that we
413 // have transitioned to a single worker thread model.
414
415 synchronized (mLock) {
416 return new ArrayList<>(mInternalAccessPoints);
417 }
Jason Monkd52356a2015-01-28 10:40:41 -0500418 }
419
420 public WifiManager getManager() {
421 return mWifiManager;
422 }
423
424 public boolean isWifiEnabled() {
425 return mWifiManager.isWifiEnabled();
426 }
427
428 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700429 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
430 * is tracking saved networks.
Peter Qiuabea7262017-05-31 10:18:17 -0700431 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
432 * directly.
Jason Monkd52356a2015-01-28 10:40:41 -0500433 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700434 public int getNumSavedNetworks() {
Peter Qiuabea7262017-05-31 10:18:17 -0700435 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
Jason Monkd52356a2015-01-28 10:40:41 -0500436 }
437
438 public boolean isConnected() {
439 return mConnected.get();
440 }
441
442 public void dump(PrintWriter pw) {
443 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400444 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500445 pw.println(" " + accessPoint);
446 }
447 }
448
Jason Monkdbd05a92015-07-06 12:55:48 -0400449 private void handleResume() {
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800450 // TODO(sghuman): Investigate removing this and replacing it with a cache eviction call
451 // instead.
Jason Monkdbd05a92015-07-06 12:55:48 -0400452 mScanResultCache.clear();
453 mSeenBssids.clear();
Jason Monkdbd05a92015-07-06 12:55:48 -0400454 }
455
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700456 private Collection<ScanResult> updateScanResultCache(final List<ScanResult> newResults) {
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800457 // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700458 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700459 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
460 continue;
461 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700462 mScanResultCache.put(newResult.BSSID, newResult);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700463 }
464
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800465 // Don't evict old results if no new scan results
466 if (!mStaleScanResults) {
467 evictOldScans();
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700468 }
469
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800470 // TODO(sghuman): Update a Map<ApKey, List<ScanResults>> variable to be reused later after
471 // double threads have been removed.
472
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700473 return mScanResultCache.values();
474 }
475
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800476 /**
477 * Remove old scan results from the cache.
478 *
479 * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when
480 * {@link #mStaleScanResults} is false.
481 */
482 private void evictOldScans() {
483 long nowMs = SystemClock.elapsedRealtime();
484 for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
485 ScanResult result = iter.next();
486 // result timestamp is in microseconds
487 if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) {
488 iter.remove();
489 }
490 }
491 }
492
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700493 private WifiConfiguration getWifiConfigurationForNetworkId(
494 int networkId, final List<WifiConfiguration> configs) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700495 if (configs != null) {
496 for (WifiConfiguration config : configs) {
497 if (mLastInfo != null && networkId == config.networkId &&
498 !(config.selfAdded && config.numAssociation == 0)) {
499 return config;
500 }
501 }
502 }
503 return null;
504 }
505
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700506 /**
507 * Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first.
508 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700509 * <p>Will not perform the update if {@link #mStaleScanResults} is true
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700510 */
511 private void updateAccessPoints() {
512 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
513 final List<ScanResult> newScanResults = mWifiManager.getScanResults();
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800514 if (isVerboseLoggingEnabled()) {
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700515 Log.i(TAG, "Fetched scan results: " + newScanResults);
516 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700517
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700518 synchronized (mLock) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700519 if(!mStaleScanResults) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700520 updateAccessPointsLocked(newScanResults, configs);
521 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700522 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700523 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700524
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700525 /**
526 * Update the internal list of access points.
527 *
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800528 * <p>Do not call directly (except for forceUpdate), use {@link #updateAccessPoints()} which
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800529 * acquires the lock first.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700530 */
531 @GuardedBy("mLock")
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700532 private void updateAccessPointsLocked(final List<ScanResult> newScanResults,
533 List<WifiConfiguration> configs) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800534 // TODO(sghuman): Reduce the synchronization time by only holding the lock when
535 // modifying lists exposed to operations on the MainThread (getAccessPoints, stopTracking,
536 // startTracking, etc).
537
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700538 WifiConfiguration connectionConfig = null;
539 if (mLastInfo != null) {
540 connectionConfig = getWifiConfigurationForNetworkId(
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800541 mLastInfo.getNetworkId(), configs);
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700542 }
543
Jason Monkd52356a2015-01-28 10:40:41 -0500544 // Swap the current access points into a cached list.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700545 List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monk30d80042015-05-08 16:54:18 -0400546 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
547
Jason Monkd52356a2015-01-28 10:40:41 -0500548 // Clear out the configs so we don't think something is saved when it isn't.
Jason Monk30d80042015-05-08 16:54:18 -0400549 for (AccessPoint accessPoint : cachedAccessPoints) {
Jason Monkd52356a2015-01-28 10:40:41 -0500550 accessPoint.clearConfig();
551 }
552
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700553 final Collection<ScanResult> results = updateScanResultCache(newScanResults);
Sanket Padawe0775a982015-08-19 14:57:46 -0700554
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800555 final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
Jason Monkd52356a2015-01-28 10:40:41 -0500556 if (configs != null) {
Jason Monkd52356a2015-01-28 10:40:41 -0500557 for (WifiConfiguration config : configs) {
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800558 configsByKey.put(AccessPoint.getKey(config), config);
Jason Monkd52356a2015-01-28 10:40:41 -0500559 }
560 }
561
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800562 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500563 if (results != null) {
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800564 // TODO(sghuman): Move this loop to updateScanResultCache and make instance variable
565 // after double handlers are removed.
566 ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500567 for (ScanResult result : results) {
568 // Ignore hidden and ad-hoc networks.
569 if (result.SSID == null || result.SSID.length() == 0 ||
570 result.capabilities.contains("[IBSS]")) {
571 continue;
572 }
573
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800574 NetworkKey key = NetworkKey.createFromScanResult(result);
Stephen Chenfde900d2017-02-14 16:40:21 -0800575 if (key != null && !mRequestedScores.contains(key)) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800576 scoresToRequest.add(key);
577 }
578
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800579 String apKey = AccessPoint.getKey(result);
580 List<ScanResult> resultList;
581 if (scanResultsByApKey.containsKey(apKey)) {
582 resultList = scanResultsByApKey.get(apKey);
583 } else {
584 resultList = new ArrayList<>();
585 scanResultsByApKey.put(apKey, resultList);
Jason Monkd52356a2015-01-28 10:40:41 -0500586 }
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800587
588 resultList.add(result);
589 }
590
591 for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) {
592 // List can not be empty as it is dynamically constructed on each iteration
593 ScanResult firstResult = entry.getValue().get(0);
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800594
595 AccessPoint accessPoint =
596 getCachedOrCreate(entry.getValue(), cachedAccessPoints);
597 if (mLastInfo != null && mLastNetworkInfo != null) {
598 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800599 }
600
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800601 // Update the matching config if there is one, to populate saved network info
602 WifiConfiguration config = configsByKey.get(entry.getKey());
603 if (config != null) {
604 accessPoint.update(config);
Jason Monkd52356a2015-01-28 10:40:41 -0500605 }
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800606
607 accessPoints.add(accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500608 }
609 }
610
Stephen Chen21f68682017-04-04 13:23:31 -0700611 requestScoresForNetworkKeys(scoresToRequest);
612 for (AccessPoint ap : accessPoints) {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700613 ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800614 }
615
Jason Monkd52356a2015-01-28 10:40:41 -0500616 // Pre-sort accessPoints to speed preference insertion
Jason Monk30d80042015-05-08 16:54:18 -0400617 Collections.sort(accessPoints);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700618
619 // Log accesspoints that were deleted
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700620 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800621 Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
622 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
623 if (prevAccessPoint.getSsid() == null)
624 continue;
625 String prevSsid = prevAccessPoint.getSsidStr();
626 boolean found = false;
627 for (AccessPoint newAccessPoint : accessPoints) {
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700628 if (newAccessPoint.getSsidStr() != null && newAccessPoint.getSsidStr()
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800629 .equals(prevSsid)) {
630 found = true;
631 break;
632 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700633 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800634 if (!found)
635 Log.d(TAG, "Did not find " + prevSsid + " in this scan");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700636 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800637 Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700638 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700639
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700640 mInternalAccessPoints.clear();
641 mInternalAccessPoints.addAll(accessPoints);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700642
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800643 conditionallyNotifyListeners();
Jason Monkd52356a2015-01-28 10:40:41 -0500644 }
645
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700646 @VisibleForTesting
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800647 AccessPoint getCachedOrCreate(
648 List<ScanResult> scanResults,
649 List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400650 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500651 for (int i = 0; i < N; i++) {
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800652 if (cache.get(i).getKey().equals(AccessPoint.getKey(scanResults.get(0)))) {
Jason Monk30d80042015-05-08 16:54:18 -0400653 AccessPoint ret = cache.remove(i);
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800654 ret.setScanResults(scanResults);
Jason Monkd52356a2015-01-28 10:40:41 -0500655 return ret;
656 }
657 }
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800658 final AccessPoint accessPoint = new AccessPoint(mContext, scanResults);
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700659 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500660 }
661
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700662 @VisibleForTesting
663 AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400664 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500665 for (int i = 0; i < N; i++) {
Jason Monk30d80042015-05-08 16:54:18 -0400666 if (cache.get(i).matches(config)) {
667 AccessPoint ret = cache.remove(i);
Jason Monkd52356a2015-01-28 10:40:41 -0500668 ret.loadConfig(config);
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800669
Jason Monkd52356a2015-01-28 10:40:41 -0500670 return ret;
671 }
672 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700673 final AccessPoint accessPoint = new AccessPoint(mContext, config);
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700674 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500675 }
676
677 private void updateNetworkInfo(NetworkInfo networkInfo) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800678
679 /* Sticky broadcasts can call this when wifi is disabled */
Jason Monke04ae8a2015-06-03 14:06:34 -0400680 if (!mWifiManager.isWifiEnabled()) {
Sundeep Ghumane8013092017-06-21 22:35:30 -0700681 clearAccessPointsAndConditionallyUpdate();
Jason Monke04ae8a2015-06-03 14:06:34 -0400682 return;
683 }
Jason Monkd52356a2015-01-28 10:40:41 -0500684
Jason Monkd52356a2015-01-28 10:40:41 -0500685 if (networkInfo != null) {
686 mLastNetworkInfo = networkInfo;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700687 if (DBG()) {
688 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo);
689 }
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800690
691 if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) {
692 mListener.onConnectedChanged();
693 }
Jason Monkd52356a2015-01-28 10:40:41 -0500694 }
695
Mitchell Wills5a42db22015-08-03 09:46:08 -0700696 WifiConfiguration connectionConfig = null;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700697
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900698 mLastInfo = mWifiManager.getConnectionInfo();
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700699 if (DBG()) {
700 Log.d(TAG, "mLastInfo set as: " + mLastInfo);
701 }
Mitchell Wills5a42db22015-08-03 09:46:08 -0700702 if (mLastInfo != null) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700703 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
704 mWifiManager.getConfiguredNetworks());
Mitchell Wills5a42db22015-08-03 09:46:08 -0700705 }
706
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700707 boolean updated = false;
708 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700709
710 synchronized (mLock) {
711 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
712 AccessPoint ap = mInternalAccessPoints.get(i);
713 boolean previouslyConnected = ap.isActive();
714 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
715 updated = true;
716 if (previouslyConnected != ap.isActive()) reorder = true;
717 }
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700718 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700719 reorder = true;
720 updated = true;
721 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800722 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700723
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800724 if (reorder) {
725 Collections.sort(mInternalAccessPoints);
726 }
727 if (updated) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800728 conditionallyNotifyListeners();
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800729 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800730 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800731 }
732
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800733 /**
734 * Clears the access point list and conditionally invokes
735 * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already
736 * empty).
737 */
Sundeep Ghumane8013092017-06-21 22:35:30 -0700738 private void clearAccessPointsAndConditionallyUpdate() {
739 synchronized (mLock) {
740 if (!mInternalAccessPoints.isEmpty()) {
741 mInternalAccessPoints.clear();
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800742 mListener.onAccessPointsChanged();
Sundeep Ghumane8013092017-06-21 22:35:30 -0700743 }
744 }
745 }
746
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800747 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700748 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800749 *
750 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700751 *
752 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800753 */
754 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700755 synchronized (mLock) {
756 boolean updated = false;
757 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700758 if (mInternalAccessPoints.get(i).update(
759 mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700760 updated = true;
761 }
Jason Monkd52356a2015-01-28 10:40:41 -0500762 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700763 if (updated) {
764 Collections.sort(mInternalAccessPoints);
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800765 conditionallyNotifyListeners();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700766 }
Jason Monkd52356a2015-01-28 10:40:41 -0500767 }
768 }
769
Jason Monkd52356a2015-01-28 10:40:41 -0500770 @VisibleForTesting
771 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
772 @Override
773 public void onReceive(Context context, Intent intent) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800774 // No work should be performed in this Receiver, instead all operations should be passed
775 // off to the WorkHandler to avoid concurrent modification exceptions.
776
Jason Monkd52356a2015-01-28 10:40:41 -0500777 String action = intent.getAction();
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700778
Jason Monkd52356a2015-01-28 10:40:41 -0500779 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800780 mWorkHandler.obtainMessage(
781 WorkHandler.MSG_UPDATE_WIFI_STATE,
782 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
783 WifiManager.WIFI_STATE_UNKNOWN),
784 0).sendToTarget();
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700785 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
786 mWorkHandler
787 .obtainMessage(
788 WorkHandler.MSG_UPDATE_ACCESS_POINTS,
789 WorkHandler.CLEAR_STALE_SCAN_RESULTS,
790 0)
791 .sendToTarget();
792 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
793 || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
Jason Monk30d80042015-05-08 16:54:18 -0400794 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Jason Monkd52356a2015-01-28 10:40:41 -0500795 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700796 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
Jason Monk30d80042015-05-08 16:54:18 -0400797 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
798 .sendToTarget();
Sundeep Ghuman2b489902017-02-22 18:17:29 -0800799 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700800 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700801 NetworkInfo info =
802 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
803 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
804 .sendToTarget();
Jason Monkd52356a2015-01-28 10:40:41 -0500805 }
806 }
807 };
808
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900809 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
810 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
811 if (network.equals(mWifiManager.getCurrentNetwork())) {
812 // We don't send a NetworkInfo object along with this message, because even if we
813 // fetch one from ConnectivityManager, it might be older than the most recent
814 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
815 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
816 }
817 }
818 }
819
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700820 @VisibleForTesting
Sundeep Ghumane8013092017-06-21 22:35:30 -0700821 final class WorkHandler extends Handler {
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800822 @VisibleForTesting static final int MSG_UPDATE_ACCESS_POINTS = 0;
Jason Monk30d80042015-05-08 16:54:18 -0400823 private static final int MSG_UPDATE_NETWORK_INFO = 1;
Jason Monkdbd05a92015-07-06 12:55:48 -0400824 private static final int MSG_RESUME = 2;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700825 private static final int MSG_UPDATE_WIFI_STATE = 3;
Jason Monk30d80042015-05-08 16:54:18 -0400826
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700827 private static final int CLEAR_STALE_SCAN_RESULTS = 1;
828
Jason Monk30d80042015-05-08 16:54:18 -0400829 public WorkHandler(Looper looper) {
830 super(looper);
831 }
832
833 @Override
834 public void handleMessage(Message msg) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800835 // TODO(sghuman): Clean up synchronization to only be used when modifying collections
836 // exposed to the MainThread (through onStart, onStop, forceUpdate).
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700837 synchronized (mLock) {
838 processMessage(msg);
839 }
840 }
841
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700842 private void processMessage(Message msg) {
843 if (!mRegistered) return;
844
Jason Monk30d80042015-05-08 16:54:18 -0400845 switch (msg.what) {
846 case MSG_UPDATE_ACCESS_POINTS:
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700847 if (msg.arg1 == CLEAR_STALE_SCAN_RESULTS) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700848 mStaleScanResults = false;
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700849 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700850 updateAccessPoints();
Jason Monk30d80042015-05-08 16:54:18 -0400851 break;
852 case MSG_UPDATE_NETWORK_INFO:
853 updateNetworkInfo((NetworkInfo) msg.obj);
854 break;
Jason Monkdbd05a92015-07-06 12:55:48 -0400855 case MSG_RESUME:
856 handleResume();
857 break;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700858 case MSG_UPDATE_WIFI_STATE:
859 if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
860 if (mScanner != null) {
861 // We only need to resume if mScanner isn't null because
862 // that means we want to be scanning.
863 mScanner.resume();
864 }
865 } else {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800866 clearAccessPointsAndConditionallyUpdate();
Mitchell Willscf0875a2016-04-18 14:54:53 -0700867 mLastInfo = null;
868 mLastNetworkInfo = null;
869 if (mScanner != null) {
870 mScanner.pause();
871 }
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700872 synchronized (mLock) {
873 mStaleScanResults = true;
874 }
Mitchell Willscf0875a2016-04-18 14:54:53 -0700875 }
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800876 mListener.onWifiStateChanged(msg.arg1);
Mitchell Willscf0875a2016-04-18 14:54:53 -0700877 break;
Jason Monk30d80042015-05-08 16:54:18 -0400878 }
879 }
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700880
881 private void removePendingMessages() {
882 removeMessages(MSG_UPDATE_ACCESS_POINTS);
883 removeMessages(MSG_UPDATE_NETWORK_INFO);
884 removeMessages(MSG_RESUME);
885 removeMessages(MSG_UPDATE_WIFI_STATE);
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700886 }
Jason Monk30d80042015-05-08 16:54:18 -0400887 }
888
Jason Monkd52356a2015-01-28 10:40:41 -0500889 @VisibleForTesting
890 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400891 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500892
Jason Monkd52356a2015-01-28 10:40:41 -0500893 private int mRetry = 0;
894
895 void resume() {
Jason Monk6572eae2015-02-05 10:34:20 -0500896 if (!hasMessages(MSG_SCAN)) {
897 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500898 }
899 }
900
Jason Monkd52356a2015-01-28 10:40:41 -0500901 void pause() {
902 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500903 removeMessages(MSG_SCAN);
904 }
905
906 @VisibleForTesting
907 boolean isScanning() {
908 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500909 }
910
911 @Override
912 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500913 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500914 if (mWifiManager.startScan()) {
915 mRetry = 0;
916 } else if (++mRetry >= 3) {
917 mRetry = 0;
918 if (mContext != null) {
919 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
920 }
921 return;
922 }
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700923 sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS);
Jason Monkd52356a2015-01-28 10:40:41 -0500924 }
925 }
926
927 /** A restricted multimap for use in constructAccessPoints */
928 private static class Multimap<K,V> {
929 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
930 /** retrieve a non-null list of values with key K */
931 List<V> getAll(K key) {
932 List<V> values = store.get(key);
933 return values != null ? values : Collections.<V>emptyList();
934 }
935
936 void put(K key, V val) {
937 List<V> curVals = store.get(key);
938 if (curVals == null) {
939 curVals = new ArrayList<V>(3);
940 store.put(key, curVals);
941 }
942 curVals.add(val);
943 }
944 }
945
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800946 /**
947 * Wraps the given {@link WifiListener} instance and executes it's methods on the Main Thread.
948 *
949 * <p>This mechanism allows us to no longer need a separate MainHandler and WorkHandler, which
950 * were previously both performing work, while avoiding errors which occur from executing
951 * callbacks which manipulate UI elements from a different thread than the MainThread.
952 */
953 private static class WifiListenerWrapper implements WifiListener {
954
955 private final Handler mHandler;
956 private final WifiListener mDelegatee;
957
958 public WifiListenerWrapper(WifiListener listener) {
959 mHandler = new Handler(Looper.getMainLooper());
960 mDelegatee = listener;
961 }
962
963 @Override
964 public void onWifiStateChanged(int state) {
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800965 if (isVerboseLoggingEnabled()) {
966 Log.i(TAG,
967 String.format("Invoking onWifiStateChanged callback with state %d", state));
968 }
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800969 mHandler.post(() -> mDelegatee.onWifiStateChanged(state));
970 }
971
972 @Override
973 public void onConnectedChanged() {
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800974 if (isVerboseLoggingEnabled()) {
975 Log.i(TAG, "Invoking onConnectedChanged callback");
976 }
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800977 mHandler.post(() -> mDelegatee.onConnectedChanged());
978 }
979
980 @Override
981 public void onAccessPointsChanged() {
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800982 if (isVerboseLoggingEnabled()) {
983 Log.i(TAG, "Invoking onAccessPointsChanged callback");
984 }
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800985 mHandler.post(() -> mDelegatee.onAccessPointsChanged());
986 }
987 }
988
Jason Monkd52356a2015-01-28 10:40:41 -0500989 public interface WifiListener {
990 /**
991 * Called when the state of Wifi has changed, the state will be one of
992 * the following.
993 *
994 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
995 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
996 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
997 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
998 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
999 * <p>
1000 *
1001 * @param state The new state of wifi.
1002 */
1003 void onWifiStateChanged(int state);
1004
1005 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001006 * Called when the connection state of wifi has changed and
1007 * {@link WifiTracker#isConnected()} should be called to get the updated state.
Jason Monkd52356a2015-01-28 10:40:41 -05001008 */
1009 void onConnectedChanged();
1010
1011 /**
1012 * Called to indicate the list of AccessPoints has been updated and
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001013 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
Jason Monkd52356a2015-01-28 10:40:41 -05001014 */
1015 void onAccessPointsChanged();
1016 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001017
1018 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001019 * Invokes {@link WifiListenerWrapper#onAccessPointsChanged()} if {@link #mStaleScanResults}
1020 * is false.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001021 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001022 private void conditionallyNotifyListeners() {
1023 if (mStaleScanResults) {
1024 return;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001025 }
1026
Sundeep Ghumanbb399912018-01-29 18:31:15 -08001027 ThreadUtils.postOnMainThread(() -> mListener.onAccessPointsChanged());
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001028 }
Jason Monkd52356a2015-01-28 10:40:41 -05001029}