blob: 8ef6a0df72041c2ea2c436b8e4aae979b2b03a80 [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 Ghumance6bab82017-04-19 16:21:46 -070018import android.annotation.MainThread;
Jason Monkd52356a2015-01-28 10:40:41 -050019import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
Sundeep Ghumane869d832017-01-25 16:23:43 -080023import android.database.ContentObserver;
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;
28import android.net.NetworkInfo.DetailedState;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080029import android.net.NetworkKey;
Lorenzo Colittiab313f82015-11-26 16:55:15 +090030import android.net.NetworkRequest;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080031import android.net.NetworkScoreManager;
32import android.net.ScoredNetwork;
Jason Monkd52356a2015-01-28 10:40:41 -050033import android.net.wifi.ScanResult;
34import android.net.wifi.WifiConfiguration;
35import android.net.wifi.WifiInfo;
36import android.net.wifi.WifiManager;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080037import android.net.wifi.WifiNetworkScoreCache;
38import android.net.wifi.WifiNetworkScoreCache.CacheListener;
Jason Monkd52356a2015-01-28 10:40:41 -050039import android.os.Handler;
Jason Monk30d80042015-05-08 16:54:18 -040040import android.os.Looper;
Jason Monkd52356a2015-01-28 10:40:41 -050041import android.os.Message;
Sundeep Ghumane869d832017-01-25 16:23:43 -080042import android.provider.Settings;
Sundeep Ghumance6bab82017-04-19 16:21:46 -070043import android.support.annotation.GuardedBy;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080044import android.util.ArraySet;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070045import android.util.Log;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -070046import android.util.SparseArray;
47import android.util.SparseIntArray;
Jason Monkd52356a2015-01-28 10:40:41 -050048import android.widget.Toast;
49
50import com.android.internal.annotations.VisibleForTesting;
51import com.android.settingslib.R;
52
53import java.io.PrintWriter;
54import java.util.ArrayList;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070055import java.util.Collection;
Jason Monkd52356a2015-01-28 10:40:41 -050056import java.util.Collections;
57import java.util.HashMap;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070058import java.util.Iterator;
Jason Monkd52356a2015-01-28 10:40:41 -050059import java.util.List;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070060import java.util.Map;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080061import java.util.Set;
Jason Monkd52356a2015-01-28 10:40:41 -050062import java.util.concurrent.atomic.AtomicBoolean;
63
64/**
65 * Tracks saved or available wifi networks and their state.
66 */
67public class WifiTracker {
Peter Qiuced37db2017-03-14 15:51:22 -070068 // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080069
Jason Monkd52356a2015-01-28 10:40:41 -050070 private static final String TAG = "WifiTracker";
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -070071 private static final boolean DBG() {
72 return Log.isLoggable(TAG, Log.DEBUG);
73 }
Jason Monkd52356a2015-01-28 10:40:41 -050074
75 /** verbose logging flag. this flag is set thru developer debugging options
76 * and used so as to assist with in-the-field WiFi connectivity debugging */
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -070077 public static boolean sVerboseLogging;
Jason Monkd52356a2015-01-28 10:40:41 -050078
79 // TODO: Allow control of this?
80 // Combo scans can take 5-6s to complete - set to 10s.
81 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -070082 private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
Jason Monkd52356a2015-01-28 10:40:41 -050083
84 private final Context mContext;
85 private final WifiManager mWifiManager;
86 private final IntentFilter mFilter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +090087 private final ConnectivityManager mConnectivityManager;
88 private final NetworkRequest mNetworkRequest;
Jason Monkd52356a2015-01-28 10:40:41 -050089 private final AtomicBoolean mConnected = new AtomicBoolean(false);
90 private final WifiListener mListener;
91 private final boolean mIncludeSaved;
92 private final boolean mIncludeScans;
Vinit Deshpandedcf00c92015-04-15 18:32:09 -070093 private final boolean mIncludePasspoints;
Sundeep Ghuman71f4a822017-04-18 19:51:46 -070094 @VisibleForTesting final MainHandler mMainHandler;
Sundeep Ghumane8013092017-06-21 22:35:30 -070095 @VisibleForTesting final WorkHandler mWorkHandler;
Jason Monk30d80042015-05-08 16:54:18 -040096
Ajay Nadathurd7b689a2016-08-31 15:07:56 -070097 private WifiTrackerNetworkCallback mNetworkCallback;
98
Sundeep Ghumance6bab82017-04-19 16:21:46 -070099 @GuardedBy("mLock")
Jason Monkd52356a2015-01-28 10:40:41 -0500100 private boolean mRegistered;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700101
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700102 /**
103 * The externally visible access point list.
104 *
105 * Updated using main handler. Clone of this collection is returned from
106 * {@link #getAccessPoints()}
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700107 */
108 private final List<AccessPoint> mAccessPoints = new ArrayList<>();
109
110 /**
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700111 * The internal list of access points, synchronized on itself.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700112 *
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700113 * Never exposed outside this class.
114 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700115 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700116 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
117
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700118 /**
119 * Synchronization lock for managing concurrency between main and worker threads.
120 *
121 * <p>This lock should be held for all background work.
122 * TODO(b/37674366): Remove the worker thread so synchronization is no longer necessary.
123 */
124 private final Object mLock = new Object();
125
Sundeep Ghuman221327d2017-05-02 15:58:39 -0700126 //visible to both worker and main thread.
127 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700128 private final AccessPointListenerAdapter mAccessPointListenerAdapter
129 = new AccessPointListenerAdapter();
130
131 private final HashMap<String, Integer> mSeenBssids = new HashMap<>();
132 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700133 private Integer mScanId = 0;
Jason Monkd52356a2015-01-28 10:40:41 -0500134
135 private NetworkInfo mLastNetworkInfo;
136 private WifiInfo mLastInfo;
137
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800138 private final NetworkScoreManager mNetworkScoreManager;
139 private final WifiNetworkScoreCache mScoreCache;
Sundeep Ghumane869d832017-01-25 16:23:43 -0800140 private boolean mNetworkScoringUiEnabled;
141 private final ContentObserver mObserver;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800142
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700143 @GuardedBy("mLock")
144 private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
145
Jason Monkd52356a2015-01-28 10:40:41 -0500146 @VisibleForTesting
147 Scanner mScanner;
Sundeep Ghuman42058742017-07-21 18:42:10 -0700148
149 @GuardedBy("mLock")
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700150 private boolean mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500151
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700152 private static IntentFilter newIntentFilter() {
153 IntentFilter filter = new IntentFilter();
154 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
155 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
156 filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
157 filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
158 filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
159 filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
160 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
161 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
162
163 return filter;
164 }
165
Jason Monk30d80042015-05-08 16:54:18 -0400166 public WifiTracker(Context context, WifiListener wifiListener,
167 boolean includeSaved, boolean includeScans) {
168 this(context, wifiListener, null, includeSaved, includeScans);
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700169 }
Jason Monk30d80042015-05-08 16:54:18 -0400170
171 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
172 boolean includeSaved, boolean includeScans) {
173 this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
174 }
175
176 public WifiTracker(Context context, WifiListener wifiListener,
177 boolean includeSaved, boolean includeScans, boolean includePasspoints) {
178 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
179 }
180
181 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700182 boolean includeSaved, boolean includeScans, boolean includePasspoints) {
Jason Monk30d80042015-05-08 16:54:18 -0400183 this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
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),
187 Looper.myLooper(), newIntentFilter());
Jason Monkd52356a2015-01-28 10:40:41 -0500188 }
189
190 @VisibleForTesting
Jason Monk30d80042015-05-08 16:54:18 -0400191 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700192 boolean includeSaved, boolean includeScans, boolean includePasspoints,
193 WifiManager wifiManager, ConnectivityManager connectivityManager,
194 NetworkScoreManager networkScoreManager, Looper currentLooper,
195 IntentFilter filter) {
Jason Monkd52356a2015-01-28 10:40:41 -0500196 if (!includeSaved && !includeScans) {
197 throw new IllegalArgumentException("Must include either saved or scans");
198 }
199 mContext = context;
Jason Monkbf3d1af2015-05-22 11:25:04 -0400200 if (currentLooper == null) {
201 // When we aren't on a looper thread, default to the main.
202 currentLooper = Looper.getMainLooper();
203 }
Jason Monk2b51cc32015-05-13 11:07:53 -0400204 mMainHandler = new MainHandler(currentLooper);
Jason Monk30d80042015-05-08 16:54:18 -0400205 mWorkHandler = new WorkHandler(
Jason Monk2b51cc32015-05-13 11:07:53 -0400206 workerLooper != null ? workerLooper : currentLooper);
Jason Monkd52356a2015-01-28 10:40:41 -0500207 mWifiManager = wifiManager;
208 mIncludeSaved = includeSaved;
209 mIncludeScans = includeScans;
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700210 mIncludePasspoints = includePasspoints;
Jason Monkd52356a2015-01-28 10:40:41 -0500211 mListener = wifiListener;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900212 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500213
214 // check if verbose logging has been turned on or off
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700215 sVerboseLogging = (mWifiManager.getVerboseLoggingLevel() > 0);
Jason Monkd52356a2015-01-28 10:40:41 -0500216
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700217 mFilter = filter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900218
219 mNetworkRequest = new NetworkRequest.Builder()
220 .clearCapabilities()
221 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
222 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800223
224 mNetworkScoreManager = networkScoreManager;
225
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700226 mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800227 @Override
228 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700229 synchronized (mLock) {
230 if (!mRegistered) return;
231 }
232
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800233 if (Log.isLoggable(TAG, Log.VERBOSE)) {
234 Log.v(TAG, "Score cache was updated with networks: " + networks);
235 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700236 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800237 }
238 });
Sundeep Ghumane869d832017-01-25 16:23:43 -0800239
240 mObserver = new ContentObserver(mWorkHandler) {
241 @Override
242 public void onChange(boolean selfChange) {
243 mNetworkScoringUiEnabled =
244 Settings.Global.getInt(
245 mContext.getContentResolver(),
246 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
247 }
248 };
Jason Monkd52356a2015-01-28 10:40:41 -0500249 }
250
Sundeep Ghuman42058742017-07-21 18:42:10 -0700251 /** Synchronously update the list of access points with the latest information. */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700252 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500253 public void forceUpdate() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700254 synchronized (mLock) {
255 mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
256 mLastInfo = mWifiManager.getConnectionInfo();
257 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700258
259 final List<ScanResult> newScanResults = mWifiManager.getScanResults();
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700260 if (sVerboseLogging) {
261 Log.i(TAG, "Fetched scan results: " + newScanResults);
262 }
263
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700264 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
Sundeep Ghuman42058742017-07-21 18:42:10 -0700265 mInternalAccessPoints.clear();
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700266 updateAccessPointsLocked(newScanResults, configs);
Sundeep Ghuman9c0b97e2017-04-13 16:05:46 -0700267
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700268 // Synchronously copy access points
269 mMainHandler.removeMessages(MainHandler.MSG_ACCESS_POINT_CHANGED);
270 mMainHandler.handleMessage(
271 Message.obtain(mMainHandler, MainHandler.MSG_ACCESS_POINT_CHANGED));
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700272 if (sVerboseLogging) {
273 Log.i(TAG, "force update - external access point list:\n" + mAccessPoints);
Sundeep Ghuman047b2992017-05-10 17:45:13 -0700274 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700275 }
Jason Monkd52356a2015-01-28 10:40:41 -0500276 }
277
278 /**
279 * Force a scan for wifi networks to happen now.
280 */
281 public void forceScan() {
282 if (mWifiManager.isWifiEnabled() && mScanner != null) {
283 mScanner.forceScan();
284 }
285 }
286
287 /**
288 * Temporarily stop scanning for wifi networks.
289 */
290 public void pauseScanning() {
291 if (mScanner != null) {
292 mScanner.pause();
Jason Monk6572eae2015-02-05 10:34:20 -0500293 mScanner = null;
Jason Monkd52356a2015-01-28 10:40:41 -0500294 }
295 }
296
297 /**
298 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800299 *
300 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500301 */
302 public void resumeScanning() {
Jason Monk6572eae2015-02-05 10:34:20 -0500303 if (mScanner == null) {
304 mScanner = new Scanner();
305 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700306
Jason Monkdbd05a92015-07-06 12:55:48 -0400307 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
Jason Monkd52356a2015-01-28 10:40:41 -0500308 if (mWifiManager.isWifiEnabled()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500309 mScanner.resume();
310 }
Jason Monkd52356a2015-01-28 10:40:41 -0500311 }
312
313 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800314 * Start tracking wifi networks and scores.
315 *
316 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500317 * then forceUpdate() must be called to populate getAccessPoints().
318 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700319 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500320 public void startTracking() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700321 synchronized (mLock) {
322 registerScoreCache();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800323
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700324 mContext.getContentResolver().registerContentObserver(
325 Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
326 false /* notifyForDescendants */,
327 mObserver);
328 mObserver.onChange(false /* selfChange */); // Set mScoringUiEnabled
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800329
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700330 resumeScanning();
331 if (!mRegistered) {
332 mContext.registerReceiver(mReceiver, mFilter);
333 // NetworkCallback objects cannot be reused. http://b/20701525 .
334 mNetworkCallback = new WifiTrackerNetworkCallback();
335 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
336 mRegistered = true;
337 }
Jason Monkd52356a2015-01-28 10:40:41 -0500338 }
339 }
340
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800341 private void registerScoreCache() {
342 mNetworkScoreManager.registerNetworkScoreCache(
343 NetworkKey.TYPE_WIFI,
344 mScoreCache,
345 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
346 }
347
348 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
349 if (keys.isEmpty()) return;
350
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700351 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800352 Log.d(TAG, "Requesting scores for Network Keys: " + keys);
353 }
354 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700355 synchronized (mLock) {
356 mRequestedScores.addAll(keys);
357 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800358 }
359
Jason Monkd52356a2015-01-28 10:40:41 -0500360 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800361 * Stop tracking wifi networks and scores.
362 *
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700363 * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700364 * ensure proper cleanup and prevent any further callbacks from occurring.
365 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700366 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700367 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
368 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
Jason Monkd52356a2015-01-28 10:40:41 -0500369 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700370 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500371 public void stopTracking() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700372 synchronized (mLock) {
373 if (mRegistered) {
374 mContext.unregisterReceiver(mReceiver);
375 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
376 mRegistered = false;
377 }
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700378 unregisterScoreCache();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700379 pauseScanning();
380 mContext.getContentResolver().unregisterContentObserver(mObserver);
381
382 mWorkHandler.removePendingMessages();
383 mMainHandler.removePendingMessages();
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700384 mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500385 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800386 }
387
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700388 private void unregisterScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800389 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800390
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700391 // We do not want to clear the existing scores in the cache, as this method is called during
392 // stop tracking on activity pause. Hence, on resumption we want the ability to show the
393 // last known, potentially stale, scores. However, by clearing requested scores, the scores
394 // will be requested again upon resumption of tracking, and if any changes have occurred
395 // the listeners (UI) will be updated accordingly.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700396 synchronized (mLock) {
397 mRequestedScores.clear();
398 }
Jason Monkd52356a2015-01-28 10:40:41 -0500399 }
400
401 /**
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700402 * Gets the current list of access points. Should be called from main thread, otherwise
403 * expect inconsistencies
Jason Monkd52356a2015-01-28 10:40:41 -0500404 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700405 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500406 public List<AccessPoint> getAccessPoints() {
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700407 return new ArrayList<>(mAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500408 }
409
410 public WifiManager getManager() {
411 return mWifiManager;
412 }
413
414 public boolean isWifiEnabled() {
415 return mWifiManager.isWifiEnabled();
416 }
417
418 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700419 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
420 * is tracking saved networks.
Peter Qiuabea7262017-05-31 10:18:17 -0700421 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
422 * directly.
Jason Monkd52356a2015-01-28 10:40:41 -0500423 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700424 public int getNumSavedNetworks() {
Peter Qiuabea7262017-05-31 10:18:17 -0700425 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
Jason Monkd52356a2015-01-28 10:40:41 -0500426 }
427
428 public boolean isConnected() {
429 return mConnected.get();
430 }
431
432 public void dump(PrintWriter pw) {
433 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400434 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500435 pw.println(" " + accessPoint);
436 }
437 }
438
Jason Monkdbd05a92015-07-06 12:55:48 -0400439 private void handleResume() {
440 mScanResultCache.clear();
441 mSeenBssids.clear();
442 mScanId = 0;
443 }
444
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700445 private Collection<ScanResult> updateScanResultCache(final List<ScanResult> newResults) {
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700446 mScanId++;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700447 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700448 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
449 continue;
450 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700451 mScanResultCache.put(newResult.BSSID, newResult);
452 mSeenBssids.put(newResult.BSSID, mScanId);
453 }
454
455 if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700456 if (DBG()) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700457 Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
458 for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
459 it.hasNext(); /* nothing */) {
460 Map.Entry<String, Integer> e = it.next();
461 if (e.getValue() < threshold) {
462 ScanResult result = mScanResultCache.get(e.getKey());
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700463 if (DBG()) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700464 mScanResultCache.remove(e.getKey());
465 it.remove();
466 }
467 }
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700468 if (DBG()) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700469 }
470
471 return mScanResultCache.values();
472 }
473
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700474 private WifiConfiguration getWifiConfigurationForNetworkId(
475 int networkId, final List<WifiConfiguration> configs) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700476 if (configs != null) {
477 for (WifiConfiguration config : configs) {
478 if (mLastInfo != null && networkId == config.networkId &&
479 !(config.selfAdded && config.numAssociation == 0)) {
480 return config;
481 }
482 }
483 }
484 return null;
485 }
486
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700487 /**
488 * Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first.
489 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700490 * <p>Will not perform the update if {@link #mStaleScanResults} is true
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700491 */
492 private void updateAccessPoints() {
493 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
494 final List<ScanResult> newScanResults = mWifiManager.getScanResults();
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700495 if (sVerboseLogging) {
496 Log.i(TAG, "Fetched scan results: " + newScanResults);
497 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700498
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700499 synchronized (mLock) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700500 if(!mStaleScanResults) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700501 updateAccessPointsLocked(newScanResults, configs);
502 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700503 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700504 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700505
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700506 /**
507 * Update the internal list of access points.
508 *
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700509 * <p>Do not called directly (except for forceUpdate), use {@link #updateAccessPoints()} which
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700510 * respects {@link #mStaleScanResults}.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700511 */
512 @GuardedBy("mLock")
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700513 private void updateAccessPointsLocked(final List<ScanResult> newScanResults,
514 List<WifiConfiguration> configs) {
515 WifiConfiguration connectionConfig = null;
516 if (mLastInfo != null) {
517 connectionConfig = getWifiConfigurationForNetworkId(
518 mLastInfo.getNetworkId(), mWifiManager.getConfiguredNetworks());
519 }
520
Jason Monkd52356a2015-01-28 10:40:41 -0500521 // Swap the current access points into a cached list.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700522 List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monk30d80042015-05-08 16:54:18 -0400523 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
524
Jason Monkd52356a2015-01-28 10:40:41 -0500525 // Clear out the configs so we don't think something is saved when it isn't.
Jason Monk30d80042015-05-08 16:54:18 -0400526 for (AccessPoint accessPoint : cachedAccessPoints) {
Jason Monkd52356a2015-01-28 10:40:41 -0500527 accessPoint.clearConfig();
528 }
529
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700530 /* Lookup table to more quickly update AccessPoints by only considering objects with the
531 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
Jason Monkd52356a2015-01-28 10:40:41 -0500532 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
533
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700534 final Collection<ScanResult> results = updateScanResultCache(newScanResults);
Sanket Padawe0775a982015-08-19 14:57:46 -0700535
Jason Monkd52356a2015-01-28 10:40:41 -0500536 if (configs != null) {
Jason Monkd52356a2015-01-28 10:40:41 -0500537 for (WifiConfiguration config : configs) {
538 if (config.selfAdded && config.numAssociation == 0) {
539 continue;
540 }
Jason Monk30d80042015-05-08 16:54:18 -0400541 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500542 if (mLastInfo != null && mLastNetworkInfo != null) {
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800543 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Jason Monkd52356a2015-01-28 10:40:41 -0500544 }
545 if (mIncludeSaved) {
Sundeep Ghumandc6cf4b2017-03-08 16:18:29 -0800546 // If saved network not present in scan result then set its Rssi to
547 // UNREACHABLE_RSSI
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800548 boolean apFound = false;
549 for (ScanResult result : results) {
550 if (result.SSID.equals(accessPoint.getSsidStr())) {
551 apFound = true;
552 break;
Sanket Padawe0775a982015-08-19 14:57:46 -0700553 }
Sanket Padawe0775a982015-08-19 14:57:46 -0700554 }
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800555 if (!apFound) {
Sundeep Ghuman54bdcfa2017-03-08 19:52:05 -0800556 accessPoint.setUnreachable();
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700557 }
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800558 accessPoints.add(accessPoint);
559 apMap.put(accessPoint.getSsidStr(), accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500560 } else {
561 // If we aren't using saved networks, drop them into the cache so that
562 // we have access to their saved info.
Jason Monk30d80042015-05-08 16:54:18 -0400563 cachedAccessPoints.add(accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500564 }
565 }
566 }
567
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800568 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500569 if (results != null) {
570 for (ScanResult result : results) {
571 // Ignore hidden and ad-hoc networks.
572 if (result.SSID == null || result.SSID.length() == 0 ||
573 result.capabilities.contains("[IBSS]")) {
574 continue;
575 }
576
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800577 NetworkKey key = NetworkKey.createFromScanResult(result);
Stephen Chenfde900d2017-02-14 16:40:21 -0800578 if (key != null && !mRequestedScores.contains(key)) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800579 scoresToRequest.add(key);
580 }
581
Jason Monkd52356a2015-01-28 10:40:41 -0500582 boolean found = false;
583 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700584 // We want to evict old scan results if are current results are not stale
585 if (accessPoint.update(result, !mStaleScanResults)) {
Jason Monkd52356a2015-01-28 10:40:41 -0500586 found = true;
587 break;
588 }
589 }
590 if (!found && mIncludeScans) {
Jason Monk30d80042015-05-08 16:54:18 -0400591 AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500592 if (mLastInfo != null && mLastNetworkInfo != null) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700593 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Jason Monkd52356a2015-01-28 10:40:41 -0500594 }
Vinit Deshpandefc406002015-04-15 18:10:55 -0700595
Vinit Deshpandea0d929e2015-06-12 15:18:44 -0700596 if (result.isPasspointNetwork()) {
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800597 // Retrieve a WifiConfiguration for a Passpoint provider that matches
598 // the given ScanResult. This is used for showing that a given AP
599 // (ScanResult) is available via a Passpoint provider (provider friendly
600 // name).
Peter Qiu0ffb89d2017-03-27 13:45:25 -0700601 try {
602 WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
603 if (config != null) {
604 accessPoint.update(config);
605 }
606 } catch (UnsupportedOperationException e) {
607 // Passpoint not supported on the device.
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700608 }
609 }
610
Jason Monk30d80042015-05-08 16:54:18 -0400611 accessPoints.add(accessPoint);
Jason Monk6980d122015-06-15 10:07:55 -0400612 apMap.put(accessPoint.getSsidStr(), accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500613 }
614 }
615 }
616
Stephen Chen21f68682017-04-04 13:23:31 -0700617 requestScoresForNetworkKeys(scoresToRequest);
618 for (AccessPoint ap : accessPoints) {
619 ap.update(mScoreCache, mNetworkScoringUiEnabled);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800620 }
621
Jason Monkd52356a2015-01-28 10:40:41 -0500622 // Pre-sort accessPoints to speed preference insertion
Jason Monk30d80042015-05-08 16:54:18 -0400623 Collections.sort(accessPoints);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700624
625 // Log accesspoints that were deleted
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700626 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800627 Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
628 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
629 if (prevAccessPoint.getSsid() == null)
630 continue;
631 String prevSsid = prevAccessPoint.getSsidStr();
632 boolean found = false;
633 for (AccessPoint newAccessPoint : accessPoints) {
634 if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid()
635 .equals(prevSsid)) {
636 found = true;
637 break;
638 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700639 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800640 if (!found)
641 Log.d(TAG, "Did not find " + prevSsid + " in this scan");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700642 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800643 Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700644 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700645
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700646 mInternalAccessPoints.clear();
647 mInternalAccessPoints.addAll(accessPoints);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700648
649 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
Jason Monkd52356a2015-01-28 10:40:41 -0500650 }
651
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700652 @VisibleForTesting
653 AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400654 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500655 for (int i = 0; i < N; i++) {
Jason Monk30d80042015-05-08 16:54:18 -0400656 if (cache.get(i).matches(result)) {
657 AccessPoint ret = cache.remove(i);
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700658 // evict old scan results only if we have fresh results
659 ret.update(result, !mStaleScanResults);
Jason Monkd52356a2015-01-28 10:40:41 -0500660 return ret;
661 }
662 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700663 final AccessPoint accessPoint = new AccessPoint(mContext, result);
664 accessPoint.setListener(mAccessPointListenerAdapter);
665 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500666 }
667
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700668 @VisibleForTesting
669 AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400670 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500671 for (int i = 0; i < N; i++) {
Jason Monk30d80042015-05-08 16:54:18 -0400672 if (cache.get(i).matches(config)) {
673 AccessPoint ret = cache.remove(i);
Jason Monkd52356a2015-01-28 10:40:41 -0500674 ret.loadConfig(config);
675 return ret;
676 }
677 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700678 final AccessPoint accessPoint = new AccessPoint(mContext, config);
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700679 accessPoint.setListener(mAccessPointListenerAdapter);
680 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500681 }
682
683 private void updateNetworkInfo(NetworkInfo networkInfo) {
Jason Monke04ae8a2015-06-03 14:06:34 -0400684 /* sticky broadcasts can call this when wifi is disabled */
685 if (!mWifiManager.isWifiEnabled()) {
686 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
Sundeep Ghumane8013092017-06-21 22:35:30 -0700687 clearAccessPointsAndConditionallyUpdate();
Jason Monke04ae8a2015-06-03 14:06:34 -0400688 return;
689 }
Jason Monkd52356a2015-01-28 10:40:41 -0500690
Jason Monke04ae8a2015-06-03 14:06:34 -0400691 if (networkInfo != null &&
692 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
693 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
694 } else {
695 mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
Jason Monkd52356a2015-01-28 10:40:41 -0500696 }
697
Jason Monkd52356a2015-01-28 10:40:41 -0500698 if (networkInfo != null) {
699 mLastNetworkInfo = networkInfo;
700 }
701
Mitchell Wills5a42db22015-08-03 09:46:08 -0700702 WifiConfiguration connectionConfig = null;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900703 mLastInfo = mWifiManager.getConnectionInfo();
Mitchell Wills5a42db22015-08-03 09:46:08 -0700704 if (mLastInfo != null) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700705 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
706 mWifiManager.getConfiguredNetworks());
Mitchell Wills5a42db22015-08-03 09:46:08 -0700707 }
708
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700709 boolean updated = false;
710 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700711
712 synchronized (mLock) {
713 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
714 AccessPoint ap = mInternalAccessPoints.get(i);
715 boolean previouslyConnected = ap.isActive();
716 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
717 updated = true;
718 if (previouslyConnected != ap.isActive()) reorder = true;
719 }
720 if (ap.update(mScoreCache, mNetworkScoringUiEnabled)) {
721 reorder = true;
722 updated = true;
723 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800724 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700725
726 if (reorder) Collections.sort(mInternalAccessPoints);
727
728 if (updated) mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800729 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800730 }
731
Sundeep Ghumane8013092017-06-21 22:35:30 -0700732 private void clearAccessPointsAndConditionallyUpdate() {
733 synchronized (mLock) {
734 if (!mInternalAccessPoints.isEmpty()) {
735 mInternalAccessPoints.clear();
736 if (!mMainHandler.hasMessages(MainHandler.MSG_ACCESS_POINT_CHANGED)) {
737 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
738 }
739 }
740 }
741 }
742
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800743 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700744 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800745 *
746 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700747 *
748 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800749 */
750 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700751 synchronized (mLock) {
752 boolean updated = false;
753 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
754 if (mInternalAccessPoints.get(i).update(mScoreCache, mNetworkScoringUiEnabled)) {
755 updated = true;
756 }
Jason Monkd52356a2015-01-28 10:40:41 -0500757 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700758 if (updated) {
759 Collections.sort(mInternalAccessPoints);
760 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
761 }
Jason Monkd52356a2015-01-28 10:40:41 -0500762 }
763 }
764
765 private void updateWifiState(int state) {
Mitchell Willscf0875a2016-04-18 14:54:53 -0700766 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
Sundeep Ghumane8013092017-06-21 22:35:30 -0700767 if (!mWifiManager.isWifiEnabled()) {
768 clearAccessPointsAndConditionallyUpdate();
769 }
Jason Monkd52356a2015-01-28 10:40:41 -0500770 }
771
772 public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700773 boolean includeScans, boolean includePasspoints) {
774 WifiTracker tracker = new WifiTracker(context,
Jason Monk30d80042015-05-08 16:54:18 -0400775 null, null, includeSaved, includeScans, includePasspoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500776 tracker.forceUpdate();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700777 tracker.copyAndNotifyListeners(false /*notifyListeners*/);
Jason Monkd52356a2015-01-28 10:40:41 -0500778 return tracker.getAccessPoints();
779 }
780
781 @VisibleForTesting
782 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
783 @Override
784 public void onReceive(Context context, Intent intent) {
785 String action = intent.getAction();
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700786
Jason Monkd52356a2015-01-28 10:40:41 -0500787 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
788 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
789 WifiManager.WIFI_STATE_UNKNOWN));
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700790 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
791 mWorkHandler
792 .obtainMessage(
793 WorkHandler.MSG_UPDATE_ACCESS_POINTS,
794 WorkHandler.CLEAR_STALE_SCAN_RESULTS,
795 0)
796 .sendToTarget();
797 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
798 || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
Jason Monk30d80042015-05-08 16:54:18 -0400799 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Jason Monkd52356a2015-01-28 10:40:41 -0500800 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700801 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
Jason Monk30d80042015-05-08 16:54:18 -0400802
Sundeep Ghuman42058742017-07-21 18:42:10 -0700803 if(mConnected.get() != info.isConnected()) {
804 mConnected.set(info.isConnected());
805 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
806 }
Jason Monk30d80042015-05-08 16:54:18 -0400807
Jason Monk30d80042015-05-08 16:54:18 -0400808 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
809 .sendToTarget();
Sundeep Ghuman2b489902017-02-22 18:17:29 -0800810 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700811 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700812 NetworkInfo info =
813 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
814 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
815 .sendToTarget();
Jason Monkd52356a2015-01-28 10:40:41 -0500816 }
817 }
818 };
819
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900820 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
821 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
822 if (network.equals(mWifiManager.getCurrentNetwork())) {
823 // We don't send a NetworkInfo object along with this message, because even if we
824 // fetch one from ConnectivityManager, it might be older than the most recent
825 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
826 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
827 }
828 }
829 }
830
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700831 @VisibleForTesting
832 final class MainHandler extends Handler {
833 @VisibleForTesting static final int MSG_CONNECTED_CHANGED = 0;
834 @VisibleForTesting static final int MSG_WIFI_STATE_CHANGED = 1;
835 @VisibleForTesting static final int MSG_ACCESS_POINT_CHANGED = 2;
Jason Monke04ae8a2015-06-03 14:06:34 -0400836 private static final int MSG_RESUME_SCANNING = 3;
837 private static final int MSG_PAUSE_SCANNING = 4;
Jason Monk30d80042015-05-08 16:54:18 -0400838
Jason Monk2b51cc32015-05-13 11:07:53 -0400839 public MainHandler(Looper looper) {
840 super(looper);
841 }
842
Jason Monk30d80042015-05-08 16:54:18 -0400843 @Override
844 public void handleMessage(Message msg) {
845 if (mListener == null) {
846 return;
847 }
848 switch (msg.what) {
849 case MSG_CONNECTED_CHANGED:
850 mListener.onConnectedChanged();
851 break;
852 case MSG_WIFI_STATE_CHANGED:
853 mListener.onWifiStateChanged(msg.arg1);
854 break;
855 case MSG_ACCESS_POINT_CHANGED:
Sundeep Ghumane8013092017-06-21 22:35:30 -0700856 // Only notify listeners of changes if we have fresh scan results, otherwise the
857 // UI will be updated with stale results. We want to copy the APs regardless,
858 // for instances where forceUpdate was invoked by the caller.
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700859 if (mStaleScanResults) {
Sundeep Ghumane8013092017-06-21 22:35:30 -0700860 copyAndNotifyListeners(false /*notifyListeners*/);
861 } else {
862 copyAndNotifyListeners(true /*notifyListeners*/);
863 mListener.onAccessPointsChanged();
864 }
Jason Monk30d80042015-05-08 16:54:18 -0400865 break;
Jason Monke04ae8a2015-06-03 14:06:34 -0400866 case MSG_RESUME_SCANNING:
867 if (mScanner != null) {
868 mScanner.resume();
869 }
870 break;
871 case MSG_PAUSE_SCANNING:
872 if (mScanner != null) {
873 mScanner.pause();
874 }
875 break;
Jason Monk30d80042015-05-08 16:54:18 -0400876 }
877 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700878
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700879 void removePendingMessages() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700880 removeMessages(MSG_ACCESS_POINT_CHANGED);
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700881 removeMessages(MSG_CONNECTED_CHANGED);
882 removeMessages(MSG_WIFI_STATE_CHANGED);
883 removeMessages(MSG_PAUSE_SCANNING);
884 removeMessages(MSG_RESUME_SCANNING);
885 }
Jason Monk30d80042015-05-08 16:54:18 -0400886 }
887
Sundeep Ghumane8013092017-06-21 22:35:30 -0700888 @VisibleForTesting
889 final class WorkHandler extends Handler {
Jason Monk30d80042015-05-08 16:54:18 -0400890 private static final int MSG_UPDATE_ACCESS_POINTS = 0;
891 private static final int MSG_UPDATE_NETWORK_INFO = 1;
Jason Monkdbd05a92015-07-06 12:55:48 -0400892 private static final int MSG_RESUME = 2;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700893 private static final int MSG_UPDATE_WIFI_STATE = 3;
Jason Monk30d80042015-05-08 16:54:18 -0400894
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700895 private static final int CLEAR_STALE_SCAN_RESULTS = 1;
896
Jason Monk30d80042015-05-08 16:54:18 -0400897 public WorkHandler(Looper looper) {
898 super(looper);
899 }
900
901 @Override
902 public void handleMessage(Message msg) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700903 synchronized (mLock) {
904 processMessage(msg);
905 }
906 }
907
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700908 private void processMessage(Message msg) {
909 if (!mRegistered) return;
910
Jason Monk30d80042015-05-08 16:54:18 -0400911 switch (msg.what) {
912 case MSG_UPDATE_ACCESS_POINTS:
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700913 if (msg.arg1 == CLEAR_STALE_SCAN_RESULTS) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700914 mStaleScanResults = false;
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700915 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700916 updateAccessPoints();
Jason Monk30d80042015-05-08 16:54:18 -0400917 break;
918 case MSG_UPDATE_NETWORK_INFO:
919 updateNetworkInfo((NetworkInfo) msg.obj);
920 break;
Jason Monkdbd05a92015-07-06 12:55:48 -0400921 case MSG_RESUME:
922 handleResume();
923 break;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700924 case MSG_UPDATE_WIFI_STATE:
925 if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
926 if (mScanner != null) {
927 // We only need to resume if mScanner isn't null because
928 // that means we want to be scanning.
929 mScanner.resume();
930 }
931 } else {
932 mLastInfo = null;
933 mLastNetworkInfo = null;
934 if (mScanner != null) {
935 mScanner.pause();
936 }
937 }
938 mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
939 .sendToTarget();
940 break;
Jason Monk30d80042015-05-08 16:54:18 -0400941 }
942 }
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700943
944 private void removePendingMessages() {
945 removeMessages(MSG_UPDATE_ACCESS_POINTS);
946 removeMessages(MSG_UPDATE_NETWORK_INFO);
947 removeMessages(MSG_RESUME);
948 removeMessages(MSG_UPDATE_WIFI_STATE);
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700949 }
Jason Monk30d80042015-05-08 16:54:18 -0400950 }
951
Jason Monkd52356a2015-01-28 10:40:41 -0500952 @VisibleForTesting
953 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400954 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500955
Jason Monkd52356a2015-01-28 10:40:41 -0500956 private int mRetry = 0;
957
958 void resume() {
Jason Monk6572eae2015-02-05 10:34:20 -0500959 if (!hasMessages(MSG_SCAN)) {
960 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500961 }
962 }
963
964 void forceScan() {
Jason Monk6572eae2015-02-05 10:34:20 -0500965 removeMessages(MSG_SCAN);
966 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500967 }
968
969 void pause() {
970 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500971 removeMessages(MSG_SCAN);
972 }
973
974 @VisibleForTesting
975 boolean isScanning() {
976 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500977 }
978
979 @Override
980 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500981 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500982 if (mWifiManager.startScan()) {
983 mRetry = 0;
984 } else if (++mRetry >= 3) {
985 mRetry = 0;
986 if (mContext != null) {
987 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
988 }
989 return;
990 }
991 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
992 }
993 }
994
995 /** A restricted multimap for use in constructAccessPoints */
996 private static class Multimap<K,V> {
997 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
998 /** retrieve a non-null list of values with key K */
999 List<V> getAll(K key) {
1000 List<V> values = store.get(key);
1001 return values != null ? values : Collections.<V>emptyList();
1002 }
1003
1004 void put(K key, V val) {
1005 List<V> curVals = store.get(key);
1006 if (curVals == null) {
1007 curVals = new ArrayList<V>(3);
1008 store.put(key, curVals);
1009 }
1010 curVals.add(val);
1011 }
1012 }
1013
1014 public interface WifiListener {
1015 /**
1016 * Called when the state of Wifi has changed, the state will be one of
1017 * the following.
1018 *
1019 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
1020 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
1021 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
1022 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
1023 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
1024 * <p>
1025 *
1026 * @param state The new state of wifi.
1027 */
1028 void onWifiStateChanged(int state);
1029
1030 /**
1031 * Called when the connection state of wifi has changed and isConnected
1032 * should be called to get the updated state.
1033 */
1034 void onConnectedChanged();
1035
1036 /**
1037 * Called to indicate the list of AccessPoints has been updated and
1038 * getAccessPoints should be called to get the latest information.
1039 */
1040 void onAccessPointsChanged();
1041 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001042
1043 /**
1044 * Helps capture notifications that were generated during AccessPoint modification. Used later
1045 * on by {@link #copyAndNotifyListeners(boolean)} to send notifications.
1046 */
1047 private static class AccessPointListenerAdapter implements AccessPoint.AccessPointListener {
1048 static final int AP_CHANGED = 1;
1049 static final int LEVEL_CHANGED = 2;
1050
1051 final SparseIntArray mPendingNotifications = new SparseIntArray();
1052
1053 @Override
1054 public void onAccessPointChanged(AccessPoint accessPoint) {
1055 int type = mPendingNotifications.get(accessPoint.mId);
1056 mPendingNotifications.put(accessPoint.mId, type | AP_CHANGED);
1057 }
1058
1059 @Override
1060 public void onLevelChanged(AccessPoint accessPoint) {
1061 int type = mPendingNotifications.get(accessPoint.mId);
1062 mPendingNotifications.put(accessPoint.mId, type | LEVEL_CHANGED);
1063 }
1064 }
1065
1066 /**
1067 * Responsible for copying access points from {@link #mInternalAccessPoints} and notifying
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001068 * accesspoint listeners.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001069 *
1070 * @param notifyListeners if true, accesspoint listeners are notified, otherwise notifications
1071 * dropped.
1072 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001073 @MainThread
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001074 private void copyAndNotifyListeners(boolean notifyListeners) {
1075 // Need to watch out for memory allocations on main thread.
1076 SparseArray<AccessPoint> oldAccessPoints = new SparseArray<>();
1077 SparseIntArray notificationMap = null;
1078 List<AccessPoint> updatedAccessPoints = new ArrayList<>();
1079
1080 for (AccessPoint accessPoint : mAccessPoints) {
1081 oldAccessPoints.put(accessPoint.mId, accessPoint);
1082 }
1083
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -07001084 if (DBG()) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001085 Log.d(TAG, "Starting to copy AP items on the MainHandler");
1086 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001087 synchronized (mLock) {
Sundeep Ghuman221327d2017-05-02 15:58:39 -07001088 if (notifyListeners) {
1089 notificationMap = mAccessPointListenerAdapter.mPendingNotifications.clone();
1090 }
1091
1092 mAccessPointListenerAdapter.mPendingNotifications.clear();
1093
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001094 for (AccessPoint internalAccessPoint : mInternalAccessPoints) {
1095 AccessPoint accessPoint = oldAccessPoints.get(internalAccessPoint.mId);
1096 if (accessPoint == null) {
1097 accessPoint = new AccessPoint(mContext, internalAccessPoint);
1098 } else {
1099 accessPoint.copyFrom(internalAccessPoint);
1100 }
1101 updatedAccessPoints.add(accessPoint);
1102 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001103 }
1104
1105 mAccessPoints.clear();
1106 mAccessPoints.addAll(updatedAccessPoints);
1107
1108 if (notificationMap != null && notificationMap.size() > 0) {
1109 for (AccessPoint accessPoint : updatedAccessPoints) {
1110 int notificationType = notificationMap.get(accessPoint.mId);
1111 AccessPoint.AccessPointListener listener = accessPoint.mAccessPointListener;
1112 if (notificationType == 0 || listener == null) {
1113 continue;
1114 }
1115
1116 if ((notificationType & AccessPointListenerAdapter.AP_CHANGED) != 0) {
1117 listener.onAccessPointChanged(accessPoint);
1118 }
1119
1120 if ((notificationType & AccessPointListenerAdapter.LEVEL_CHANGED) != 0) {
1121 listener.onLevelChanged(accessPoint);
1122 }
1123 }
1124 }
1125 }
Jason Monkd52356a2015-01-28 10:40:41 -05001126}