blob: a242570d6930f35bfb90c58aa4b4da9ae315b99b [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
Jason Monk30d80042015-05-08 16:54:18 -0400152 public WifiTracker(Context context, WifiListener wifiListener,
153 boolean includeSaved, boolean includeScans) {
154 this(context, wifiListener, null, includeSaved, includeScans);
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700155 }
Jason Monk30d80042015-05-08 16:54:18 -0400156
157 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
158 boolean includeSaved, boolean includeScans) {
159 this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
160 }
161
162 public WifiTracker(Context context, WifiListener wifiListener,
163 boolean includeSaved, boolean includeScans, boolean includePasspoints) {
164 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
165 }
166
167 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
168 boolean includeSaved, boolean includeScans, boolean includePasspoints) {
169 this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900170 context.getSystemService(WifiManager.class),
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800171 context.getSystemService(ConnectivityManager.class),
172 context.getSystemService(NetworkScoreManager.class), Looper.myLooper()
173 );
Jason Monkd52356a2015-01-28 10:40:41 -0500174 }
175
176 @VisibleForTesting
Jason Monk30d80042015-05-08 16:54:18 -0400177 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
178 boolean includeSaved, boolean includeScans, boolean includePasspoints,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900179 WifiManager wifiManager, ConnectivityManager connectivityManager,
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800180 NetworkScoreManager networkScoreManager, Looper currentLooper) {
Jason Monkd52356a2015-01-28 10:40:41 -0500181 if (!includeSaved && !includeScans) {
182 throw new IllegalArgumentException("Must include either saved or scans");
183 }
184 mContext = context;
Jason Monkbf3d1af2015-05-22 11:25:04 -0400185 if (currentLooper == null) {
186 // When we aren't on a looper thread, default to the main.
187 currentLooper = Looper.getMainLooper();
188 }
Jason Monk2b51cc32015-05-13 11:07:53 -0400189 mMainHandler = new MainHandler(currentLooper);
Jason Monk30d80042015-05-08 16:54:18 -0400190 mWorkHandler = new WorkHandler(
Jason Monk2b51cc32015-05-13 11:07:53 -0400191 workerLooper != null ? workerLooper : currentLooper);
Jason Monkd52356a2015-01-28 10:40:41 -0500192 mWifiManager = wifiManager;
193 mIncludeSaved = includeSaved;
194 mIncludeScans = includeScans;
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700195 mIncludePasspoints = includePasspoints;
Jason Monkd52356a2015-01-28 10:40:41 -0500196 mListener = wifiListener;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900197 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500198
199 // check if verbose logging has been turned on or off
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700200 sVerboseLogging = (mWifiManager.getVerboseLoggingLevel() > 0);
Jason Monkd52356a2015-01-28 10:40:41 -0500201
202 mFilter = new IntentFilter();
203 mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
204 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
205 mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
206 mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
207 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
208 mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
209 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700210 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900211
212 mNetworkRequest = new NetworkRequest.Builder()
213 .clearCapabilities()
214 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
215 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800216
217 mNetworkScoreManager = networkScoreManager;
218
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700219 mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800220 @Override
221 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700222 synchronized (mLock) {
223 if (!mRegistered) return;
224 }
225
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800226 if (Log.isLoggable(TAG, Log.VERBOSE)) {
227 Log.v(TAG, "Score cache was updated with networks: " + networks);
228 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700229 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800230 }
231 });
Sundeep Ghumane869d832017-01-25 16:23:43 -0800232
233 mObserver = new ContentObserver(mWorkHandler) {
234 @Override
235 public void onChange(boolean selfChange) {
236 mNetworkScoringUiEnabled =
237 Settings.Global.getInt(
238 mContext.getContentResolver(),
239 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
240 }
241 };
Jason Monkd52356a2015-01-28 10:40:41 -0500242 }
243
Sundeep Ghuman42058742017-07-21 18:42:10 -0700244 /** Synchronously update the list of access points with the latest information. */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700245 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500246 public void forceUpdate() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700247 synchronized (mLock) {
248 mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
249 mLastInfo = mWifiManager.getConnectionInfo();
250 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700251
252 final List<ScanResult> newScanResults = mWifiManager.getScanResults();
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700253 if (sVerboseLogging) {
254 Log.i(TAG, "Fetched scan results: " + newScanResults);
255 }
256
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700257 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
Sundeep Ghuman42058742017-07-21 18:42:10 -0700258 mInternalAccessPoints.clear();
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700259 updateAccessPointsLocked(newScanResults, configs);
Sundeep Ghuman9c0b97e2017-04-13 16:05:46 -0700260
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700261 // Synchronously copy access points
262 mMainHandler.removeMessages(MainHandler.MSG_ACCESS_POINT_CHANGED);
263 mMainHandler.handleMessage(
264 Message.obtain(mMainHandler, MainHandler.MSG_ACCESS_POINT_CHANGED));
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700265 if (sVerboseLogging) {
266 Log.i(TAG, "force update - external access point list:\n" + mAccessPoints);
Sundeep Ghuman047b2992017-05-10 17:45:13 -0700267 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700268 }
Jason Monkd52356a2015-01-28 10:40:41 -0500269 }
270
271 /**
272 * Force a scan for wifi networks to happen now.
273 */
274 public void forceScan() {
275 if (mWifiManager.isWifiEnabled() && mScanner != null) {
276 mScanner.forceScan();
277 }
278 }
279
280 /**
281 * Temporarily stop scanning for wifi networks.
282 */
283 public void pauseScanning() {
284 if (mScanner != null) {
285 mScanner.pause();
Jason Monk6572eae2015-02-05 10:34:20 -0500286 mScanner = null;
Jason Monkd52356a2015-01-28 10:40:41 -0500287 }
288 }
289
290 /**
291 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800292 *
293 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500294 */
295 public void resumeScanning() {
Jason Monk6572eae2015-02-05 10:34:20 -0500296 if (mScanner == null) {
297 mScanner = new Scanner();
298 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700299
Jason Monkdbd05a92015-07-06 12:55:48 -0400300 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
Jason Monkd52356a2015-01-28 10:40:41 -0500301 if (mWifiManager.isWifiEnabled()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500302 mScanner.resume();
303 }
Jason Monkd52356a2015-01-28 10:40:41 -0500304 }
305
306 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800307 * Start tracking wifi networks and scores.
308 *
309 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500310 * then forceUpdate() must be called to populate getAccessPoints().
311 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700312 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500313 public void startTracking() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700314 synchronized (mLock) {
315 registerScoreCache();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800316
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700317 mContext.getContentResolver().registerContentObserver(
318 Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
319 false /* notifyForDescendants */,
320 mObserver);
321 mObserver.onChange(false /* selfChange */); // Set mScoringUiEnabled
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800322
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700323 resumeScanning();
324 if (!mRegistered) {
325 mContext.registerReceiver(mReceiver, mFilter);
326 // NetworkCallback objects cannot be reused. http://b/20701525 .
327 mNetworkCallback = new WifiTrackerNetworkCallback();
328 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
329 mRegistered = true;
330 }
Jason Monkd52356a2015-01-28 10:40:41 -0500331 }
332 }
333
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800334 private void registerScoreCache() {
335 mNetworkScoreManager.registerNetworkScoreCache(
336 NetworkKey.TYPE_WIFI,
337 mScoreCache,
338 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
339 }
340
341 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
342 if (keys.isEmpty()) return;
343
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700344 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800345 Log.d(TAG, "Requesting scores for Network Keys: " + keys);
346 }
347 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700348 synchronized (mLock) {
349 mRequestedScores.addAll(keys);
350 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800351 }
352
Jason Monkd52356a2015-01-28 10:40:41 -0500353 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800354 * Stop tracking wifi networks and scores.
355 *
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700356 * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700357 * ensure proper cleanup and prevent any further callbacks from occurring.
358 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700359 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700360 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
361 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
Jason Monkd52356a2015-01-28 10:40:41 -0500362 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700363 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500364 public void stopTracking() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700365 synchronized (mLock) {
366 if (mRegistered) {
367 mContext.unregisterReceiver(mReceiver);
368 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
369 mRegistered = false;
370 }
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700371 unregisterScoreCache();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700372 pauseScanning();
373 mContext.getContentResolver().unregisterContentObserver(mObserver);
374
375 mWorkHandler.removePendingMessages();
376 mMainHandler.removePendingMessages();
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700377 mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500378 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800379 }
380
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700381 private void unregisterScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800382 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800383
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700384 // We do not want to clear the existing scores in the cache, as this method is called during
385 // stop tracking on activity pause. Hence, on resumption we want the ability to show the
386 // last known, potentially stale, scores. However, by clearing requested scores, the scores
387 // will be requested again upon resumption of tracking, and if any changes have occurred
388 // the listeners (UI) will be updated accordingly.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700389 synchronized (mLock) {
390 mRequestedScores.clear();
391 }
Jason Monkd52356a2015-01-28 10:40:41 -0500392 }
393
394 /**
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700395 * Gets the current list of access points. Should be called from main thread, otherwise
396 * expect inconsistencies
Jason Monkd52356a2015-01-28 10:40:41 -0500397 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700398 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500399 public List<AccessPoint> getAccessPoints() {
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700400 return new ArrayList<>(mAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500401 }
402
403 public WifiManager getManager() {
404 return mWifiManager;
405 }
406
407 public boolean isWifiEnabled() {
408 return mWifiManager.isWifiEnabled();
409 }
410
411 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700412 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
413 * is tracking saved networks.
Peter Qiuabea7262017-05-31 10:18:17 -0700414 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
415 * directly.
Jason Monkd52356a2015-01-28 10:40:41 -0500416 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700417 public int getNumSavedNetworks() {
Peter Qiuabea7262017-05-31 10:18:17 -0700418 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
Jason Monkd52356a2015-01-28 10:40:41 -0500419 }
420
421 public boolean isConnected() {
422 return mConnected.get();
423 }
424
425 public void dump(PrintWriter pw) {
426 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400427 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500428 pw.println(" " + accessPoint);
429 }
430 }
431
Jason Monkdbd05a92015-07-06 12:55:48 -0400432 private void handleResume() {
433 mScanResultCache.clear();
434 mSeenBssids.clear();
435 mScanId = 0;
436 }
437
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700438 private Collection<ScanResult> updateScanResultCache(final List<ScanResult> newResults) {
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700439 mScanId++;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700440 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700441 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
442 continue;
443 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700444 mScanResultCache.put(newResult.BSSID, newResult);
445 mSeenBssids.put(newResult.BSSID, mScanId);
446 }
447
448 if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700449 if (DBG()) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700450 Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
451 for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
452 it.hasNext(); /* nothing */) {
453 Map.Entry<String, Integer> e = it.next();
454 if (e.getValue() < threshold) {
455 ScanResult result = mScanResultCache.get(e.getKey());
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700456 if (DBG()) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700457 mScanResultCache.remove(e.getKey());
458 it.remove();
459 }
460 }
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700461 if (DBG()) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700462 }
463
464 return mScanResultCache.values();
465 }
466
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700467 private WifiConfiguration getWifiConfigurationForNetworkId(
468 int networkId, final List<WifiConfiguration> configs) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700469 if (configs != null) {
470 for (WifiConfiguration config : configs) {
471 if (mLastInfo != null && networkId == config.networkId &&
472 !(config.selfAdded && config.numAssociation == 0)) {
473 return config;
474 }
475 }
476 }
477 return null;
478 }
479
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700480 /**
481 * Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first.
482 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700483 * <p>Will not perform the update if {@link #mStaleScanResults} is true
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700484 */
485 private void updateAccessPoints() {
486 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
487 final List<ScanResult> newScanResults = mWifiManager.getScanResults();
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700488 if (sVerboseLogging) {
489 Log.i(TAG, "Fetched scan results: " + newScanResults);
490 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700491
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700492 synchronized (mLock) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700493 if(!mStaleScanResults) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700494 updateAccessPointsLocked(newScanResults, configs);
495 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700496 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700497 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700498
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700499 /**
500 * Update the internal list of access points.
501 *
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700502 * <p>Do not called directly (except for forceUpdate), use {@link #updateAccessPoints()} which
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700503 * respects {@link #mStaleScanResults}.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700504 */
505 @GuardedBy("mLock")
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700506 private void updateAccessPointsLocked(final List<ScanResult> newScanResults,
507 List<WifiConfiguration> configs) {
508 WifiConfiguration connectionConfig = null;
509 if (mLastInfo != null) {
510 connectionConfig = getWifiConfigurationForNetworkId(
511 mLastInfo.getNetworkId(), mWifiManager.getConfiguredNetworks());
512 }
513
Jason Monkd52356a2015-01-28 10:40:41 -0500514 // Swap the current access points into a cached list.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700515 List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monk30d80042015-05-08 16:54:18 -0400516 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
517
Jason Monkd52356a2015-01-28 10:40:41 -0500518 // Clear out the configs so we don't think something is saved when it isn't.
Jason Monk30d80042015-05-08 16:54:18 -0400519 for (AccessPoint accessPoint : cachedAccessPoints) {
Jason Monkd52356a2015-01-28 10:40:41 -0500520 accessPoint.clearConfig();
521 }
522
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700523 /* Lookup table to more quickly update AccessPoints by only considering objects with the
524 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
Jason Monkd52356a2015-01-28 10:40:41 -0500525 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
526
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700527 final Collection<ScanResult> results = updateScanResultCache(newScanResults);
Sanket Padawe0775a982015-08-19 14:57:46 -0700528
Jason Monkd52356a2015-01-28 10:40:41 -0500529 if (configs != null) {
Jason Monkd52356a2015-01-28 10:40:41 -0500530 for (WifiConfiguration config : configs) {
531 if (config.selfAdded && config.numAssociation == 0) {
532 continue;
533 }
Jason Monk30d80042015-05-08 16:54:18 -0400534 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500535 if (mLastInfo != null && mLastNetworkInfo != null) {
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800536 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Jason Monkd52356a2015-01-28 10:40:41 -0500537 }
538 if (mIncludeSaved) {
Sundeep Ghumandc6cf4b2017-03-08 16:18:29 -0800539 // If saved network not present in scan result then set its Rssi to
540 // UNREACHABLE_RSSI
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800541 boolean apFound = false;
542 for (ScanResult result : results) {
543 if (result.SSID.equals(accessPoint.getSsidStr())) {
544 apFound = true;
545 break;
Sanket Padawe0775a982015-08-19 14:57:46 -0700546 }
Sanket Padawe0775a982015-08-19 14:57:46 -0700547 }
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800548 if (!apFound) {
Sundeep Ghuman54bdcfa2017-03-08 19:52:05 -0800549 accessPoint.setUnreachable();
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700550 }
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800551 accessPoints.add(accessPoint);
552 apMap.put(accessPoint.getSsidStr(), accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500553 } else {
554 // If we aren't using saved networks, drop them into the cache so that
555 // we have access to their saved info.
Jason Monk30d80042015-05-08 16:54:18 -0400556 cachedAccessPoints.add(accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500557 }
558 }
559 }
560
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800561 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500562 if (results != null) {
563 for (ScanResult result : results) {
564 // Ignore hidden and ad-hoc networks.
565 if (result.SSID == null || result.SSID.length() == 0 ||
566 result.capabilities.contains("[IBSS]")) {
567 continue;
568 }
569
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800570 NetworkKey key = NetworkKey.createFromScanResult(result);
Stephen Chenfde900d2017-02-14 16:40:21 -0800571 if (key != null && !mRequestedScores.contains(key)) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800572 scoresToRequest.add(key);
573 }
574
Jason Monkd52356a2015-01-28 10:40:41 -0500575 boolean found = false;
576 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700577 // We want to evict old scan results if are current results are not stale
578 if (accessPoint.update(result, !mStaleScanResults)) {
Jason Monkd52356a2015-01-28 10:40:41 -0500579 found = true;
580 break;
581 }
582 }
583 if (!found && mIncludeScans) {
Jason Monk30d80042015-05-08 16:54:18 -0400584 AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500585 if (mLastInfo != null && mLastNetworkInfo != null) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700586 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Jason Monkd52356a2015-01-28 10:40:41 -0500587 }
Vinit Deshpandefc406002015-04-15 18:10:55 -0700588
Vinit Deshpandea0d929e2015-06-12 15:18:44 -0700589 if (result.isPasspointNetwork()) {
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800590 // Retrieve a WifiConfiguration for a Passpoint provider that matches
591 // the given ScanResult. This is used for showing that a given AP
592 // (ScanResult) is available via a Passpoint provider (provider friendly
593 // name).
Peter Qiu0ffb89d2017-03-27 13:45:25 -0700594 try {
595 WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
596 if (config != null) {
597 accessPoint.update(config);
598 }
599 } catch (UnsupportedOperationException e) {
600 // Passpoint not supported on the device.
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700601 }
602 }
603
Jason Monk30d80042015-05-08 16:54:18 -0400604 accessPoints.add(accessPoint);
Jason Monk6980d122015-06-15 10:07:55 -0400605 apMap.put(accessPoint.getSsidStr(), accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500606 }
607 }
608 }
609
Stephen Chen21f68682017-04-04 13:23:31 -0700610 requestScoresForNetworkKeys(scoresToRequest);
611 for (AccessPoint ap : accessPoints) {
612 ap.update(mScoreCache, mNetworkScoringUiEnabled);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800613 }
614
Jason Monkd52356a2015-01-28 10:40:41 -0500615 // Pre-sort accessPoints to speed preference insertion
Jason Monk30d80042015-05-08 16:54:18 -0400616 Collections.sort(accessPoints);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700617
618 // Log accesspoints that were deleted
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700619 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800620 Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
621 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
622 if (prevAccessPoint.getSsid() == null)
623 continue;
624 String prevSsid = prevAccessPoint.getSsidStr();
625 boolean found = false;
626 for (AccessPoint newAccessPoint : accessPoints) {
627 if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid()
628 .equals(prevSsid)) {
629 found = true;
630 break;
631 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700632 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800633 if (!found)
634 Log.d(TAG, "Did not find " + prevSsid + " in this scan");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700635 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800636 Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700637 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700638
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700639 mInternalAccessPoints.clear();
640 mInternalAccessPoints.addAll(accessPoints);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700641
642 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
Jason Monkd52356a2015-01-28 10:40:41 -0500643 }
644
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700645 @VisibleForTesting
646 AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400647 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500648 for (int i = 0; i < N; i++) {
Jason Monk30d80042015-05-08 16:54:18 -0400649 if (cache.get(i).matches(result)) {
650 AccessPoint ret = cache.remove(i);
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700651 // evict old scan results only if we have fresh results
652 ret.update(result, !mStaleScanResults);
Jason Monkd52356a2015-01-28 10:40:41 -0500653 return ret;
654 }
655 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700656 final AccessPoint accessPoint = new AccessPoint(mContext, result);
657 accessPoint.setListener(mAccessPointListenerAdapter);
658 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500659 }
660
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700661 @VisibleForTesting
662 AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400663 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500664 for (int i = 0; i < N; i++) {
Jason Monk30d80042015-05-08 16:54:18 -0400665 if (cache.get(i).matches(config)) {
666 AccessPoint ret = cache.remove(i);
Jason Monkd52356a2015-01-28 10:40:41 -0500667 ret.loadConfig(config);
668 return ret;
669 }
670 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700671 final AccessPoint accessPoint = new AccessPoint(mContext, config);
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700672 accessPoint.setListener(mAccessPointListenerAdapter);
673 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500674 }
675
676 private void updateNetworkInfo(NetworkInfo networkInfo) {
Jason Monke04ae8a2015-06-03 14:06:34 -0400677 /* sticky broadcasts can call this when wifi is disabled */
678 if (!mWifiManager.isWifiEnabled()) {
679 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
Sundeep Ghumane8013092017-06-21 22:35:30 -0700680 clearAccessPointsAndConditionallyUpdate();
Jason Monke04ae8a2015-06-03 14:06:34 -0400681 return;
682 }
Jason Monkd52356a2015-01-28 10:40:41 -0500683
Jason Monke04ae8a2015-06-03 14:06:34 -0400684 if (networkInfo != null &&
685 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
686 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
687 } else {
688 mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
Jason Monkd52356a2015-01-28 10:40:41 -0500689 }
690
Jason Monkd52356a2015-01-28 10:40:41 -0500691 if (networkInfo != null) {
692 mLastNetworkInfo = networkInfo;
693 }
694
Mitchell Wills5a42db22015-08-03 09:46:08 -0700695 WifiConfiguration connectionConfig = null;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900696 mLastInfo = mWifiManager.getConnectionInfo();
Mitchell Wills5a42db22015-08-03 09:46:08 -0700697 if (mLastInfo != null) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700698 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
699 mWifiManager.getConfiguredNetworks());
Mitchell Wills5a42db22015-08-03 09:46:08 -0700700 }
701
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700702 boolean updated = false;
703 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700704
705 synchronized (mLock) {
706 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
707 AccessPoint ap = mInternalAccessPoints.get(i);
708 boolean previouslyConnected = ap.isActive();
709 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
710 updated = true;
711 if (previouslyConnected != ap.isActive()) reorder = true;
712 }
713 if (ap.update(mScoreCache, mNetworkScoringUiEnabled)) {
714 reorder = true;
715 updated = true;
716 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800717 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700718
719 if (reorder) Collections.sort(mInternalAccessPoints);
720
721 if (updated) mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800722 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800723 }
724
Sundeep Ghumane8013092017-06-21 22:35:30 -0700725 private void clearAccessPointsAndConditionallyUpdate() {
726 synchronized (mLock) {
727 if (!mInternalAccessPoints.isEmpty()) {
728 mInternalAccessPoints.clear();
729 if (!mMainHandler.hasMessages(MainHandler.MSG_ACCESS_POINT_CHANGED)) {
730 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
731 }
732 }
733 }
734 }
735
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800736 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700737 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800738 *
739 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700740 *
741 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800742 */
743 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700744 synchronized (mLock) {
745 boolean updated = false;
746 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
747 if (mInternalAccessPoints.get(i).update(mScoreCache, mNetworkScoringUiEnabled)) {
748 updated = true;
749 }
Jason Monkd52356a2015-01-28 10:40:41 -0500750 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700751 if (updated) {
752 Collections.sort(mInternalAccessPoints);
753 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
754 }
Jason Monkd52356a2015-01-28 10:40:41 -0500755 }
756 }
757
758 private void updateWifiState(int state) {
Mitchell Willscf0875a2016-04-18 14:54:53 -0700759 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
Sundeep Ghumane8013092017-06-21 22:35:30 -0700760 if (!mWifiManager.isWifiEnabled()) {
761 clearAccessPointsAndConditionallyUpdate();
762 }
Jason Monkd52356a2015-01-28 10:40:41 -0500763 }
764
765 public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700766 boolean includeScans, boolean includePasspoints) {
767 WifiTracker tracker = new WifiTracker(context,
Jason Monk30d80042015-05-08 16:54:18 -0400768 null, null, includeSaved, includeScans, includePasspoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500769 tracker.forceUpdate();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700770 tracker.copyAndNotifyListeners(false /*notifyListeners*/);
Jason Monkd52356a2015-01-28 10:40:41 -0500771 return tracker.getAccessPoints();
772 }
773
774 @VisibleForTesting
775 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
776 @Override
777 public void onReceive(Context context, Intent intent) {
778 String action = intent.getAction();
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700779
Jason Monkd52356a2015-01-28 10:40:41 -0500780 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
781 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
782 WifiManager.WIFI_STATE_UNKNOWN));
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700783 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
784 mWorkHandler
785 .obtainMessage(
786 WorkHandler.MSG_UPDATE_ACCESS_POINTS,
787 WorkHandler.CLEAR_STALE_SCAN_RESULTS,
788 0)
789 .sendToTarget();
790 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
791 || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
Jason Monk30d80042015-05-08 16:54:18 -0400792 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Jason Monkd52356a2015-01-28 10:40:41 -0500793 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700794 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
Jason Monk30d80042015-05-08 16:54:18 -0400795
Sundeep Ghuman42058742017-07-21 18:42:10 -0700796 if(mConnected.get() != info.isConnected()) {
797 mConnected.set(info.isConnected());
798 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
799 }
Jason Monk30d80042015-05-08 16:54:18 -0400800
Jason Monk30d80042015-05-08 16:54:18 -0400801 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
802 .sendToTarget();
Sundeep Ghuman2b489902017-02-22 18:17:29 -0800803 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700804 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700805 NetworkInfo info =
806 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
807 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
808 .sendToTarget();
Jason Monkd52356a2015-01-28 10:40:41 -0500809 }
810 }
811 };
812
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900813 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
814 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
815 if (network.equals(mWifiManager.getCurrentNetwork())) {
816 // We don't send a NetworkInfo object along with this message, because even if we
817 // fetch one from ConnectivityManager, it might be older than the most recent
818 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
819 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
820 }
821 }
822 }
823
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700824 @VisibleForTesting
825 final class MainHandler extends Handler {
826 @VisibleForTesting static final int MSG_CONNECTED_CHANGED = 0;
827 @VisibleForTesting static final int MSG_WIFI_STATE_CHANGED = 1;
828 @VisibleForTesting static final int MSG_ACCESS_POINT_CHANGED = 2;
Jason Monke04ae8a2015-06-03 14:06:34 -0400829 private static final int MSG_RESUME_SCANNING = 3;
830 private static final int MSG_PAUSE_SCANNING = 4;
Jason Monk30d80042015-05-08 16:54:18 -0400831
Jason Monk2b51cc32015-05-13 11:07:53 -0400832 public MainHandler(Looper looper) {
833 super(looper);
834 }
835
Jason Monk30d80042015-05-08 16:54:18 -0400836 @Override
837 public void handleMessage(Message msg) {
838 if (mListener == null) {
839 return;
840 }
841 switch (msg.what) {
842 case MSG_CONNECTED_CHANGED:
843 mListener.onConnectedChanged();
844 break;
845 case MSG_WIFI_STATE_CHANGED:
846 mListener.onWifiStateChanged(msg.arg1);
847 break;
848 case MSG_ACCESS_POINT_CHANGED:
Sundeep Ghumane8013092017-06-21 22:35:30 -0700849 // Only notify listeners of changes if we have fresh scan results, otherwise the
850 // UI will be updated with stale results. We want to copy the APs regardless,
851 // for instances where forceUpdate was invoked by the caller.
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700852 if (mStaleScanResults) {
Sundeep Ghumane8013092017-06-21 22:35:30 -0700853 copyAndNotifyListeners(false /*notifyListeners*/);
854 } else {
855 copyAndNotifyListeners(true /*notifyListeners*/);
856 mListener.onAccessPointsChanged();
857 }
Jason Monk30d80042015-05-08 16:54:18 -0400858 break;
Jason Monke04ae8a2015-06-03 14:06:34 -0400859 case MSG_RESUME_SCANNING:
860 if (mScanner != null) {
861 mScanner.resume();
862 }
863 break;
864 case MSG_PAUSE_SCANNING:
865 if (mScanner != null) {
866 mScanner.pause();
867 }
868 break;
Jason Monk30d80042015-05-08 16:54:18 -0400869 }
870 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700871
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700872 void removePendingMessages() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700873 removeMessages(MSG_ACCESS_POINT_CHANGED);
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700874 removeMessages(MSG_CONNECTED_CHANGED);
875 removeMessages(MSG_WIFI_STATE_CHANGED);
876 removeMessages(MSG_PAUSE_SCANNING);
877 removeMessages(MSG_RESUME_SCANNING);
878 }
Jason Monk30d80042015-05-08 16:54:18 -0400879 }
880
Sundeep Ghumane8013092017-06-21 22:35:30 -0700881 @VisibleForTesting
882 final class WorkHandler extends Handler {
Jason Monk30d80042015-05-08 16:54:18 -0400883 private static final int MSG_UPDATE_ACCESS_POINTS = 0;
884 private static final int MSG_UPDATE_NETWORK_INFO = 1;
Jason Monkdbd05a92015-07-06 12:55:48 -0400885 private static final int MSG_RESUME = 2;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700886 private static final int MSG_UPDATE_WIFI_STATE = 3;
Jason Monk30d80042015-05-08 16:54:18 -0400887
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700888 private static final int CLEAR_STALE_SCAN_RESULTS = 1;
889
Jason Monk30d80042015-05-08 16:54:18 -0400890 public WorkHandler(Looper looper) {
891 super(looper);
892 }
893
894 @Override
895 public void handleMessage(Message msg) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700896 synchronized (mLock) {
897 processMessage(msg);
898 }
899 }
900
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700901 private void processMessage(Message msg) {
902 if (!mRegistered) return;
903
Jason Monk30d80042015-05-08 16:54:18 -0400904 switch (msg.what) {
905 case MSG_UPDATE_ACCESS_POINTS:
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700906 if (msg.arg1 == CLEAR_STALE_SCAN_RESULTS) {
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700907 mStaleScanResults = false;
Sundeep Ghumand2b84a82017-07-05 14:19:30 -0700908 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700909 updateAccessPoints();
Jason Monk30d80042015-05-08 16:54:18 -0400910 break;
911 case MSG_UPDATE_NETWORK_INFO:
912 updateNetworkInfo((NetworkInfo) msg.obj);
913 break;
Jason Monkdbd05a92015-07-06 12:55:48 -0400914 case MSG_RESUME:
915 handleResume();
916 break;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700917 case MSG_UPDATE_WIFI_STATE:
918 if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
919 if (mScanner != null) {
920 // We only need to resume if mScanner isn't null because
921 // that means we want to be scanning.
922 mScanner.resume();
923 }
924 } else {
925 mLastInfo = null;
926 mLastNetworkInfo = null;
927 if (mScanner != null) {
928 mScanner.pause();
929 }
930 }
931 mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
932 .sendToTarget();
933 break;
Jason Monk30d80042015-05-08 16:54:18 -0400934 }
935 }
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700936
937 private void removePendingMessages() {
938 removeMessages(MSG_UPDATE_ACCESS_POINTS);
939 removeMessages(MSG_UPDATE_NETWORK_INFO);
940 removeMessages(MSG_RESUME);
941 removeMessages(MSG_UPDATE_WIFI_STATE);
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700942 }
Jason Monk30d80042015-05-08 16:54:18 -0400943 }
944
Jason Monkd52356a2015-01-28 10:40:41 -0500945 @VisibleForTesting
946 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400947 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500948
Jason Monkd52356a2015-01-28 10:40:41 -0500949 private int mRetry = 0;
950
951 void resume() {
Jason Monk6572eae2015-02-05 10:34:20 -0500952 if (!hasMessages(MSG_SCAN)) {
953 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500954 }
955 }
956
957 void forceScan() {
Jason Monk6572eae2015-02-05 10:34:20 -0500958 removeMessages(MSG_SCAN);
959 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500960 }
961
962 void pause() {
963 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500964 removeMessages(MSG_SCAN);
965 }
966
967 @VisibleForTesting
968 boolean isScanning() {
969 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500970 }
971
972 @Override
973 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500974 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500975 if (mWifiManager.startScan()) {
976 mRetry = 0;
977 } else if (++mRetry >= 3) {
978 mRetry = 0;
979 if (mContext != null) {
980 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
981 }
982 return;
983 }
984 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
985 }
986 }
987
988 /** A restricted multimap for use in constructAccessPoints */
989 private static class Multimap<K,V> {
990 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
991 /** retrieve a non-null list of values with key K */
992 List<V> getAll(K key) {
993 List<V> values = store.get(key);
994 return values != null ? values : Collections.<V>emptyList();
995 }
996
997 void put(K key, V val) {
998 List<V> curVals = store.get(key);
999 if (curVals == null) {
1000 curVals = new ArrayList<V>(3);
1001 store.put(key, curVals);
1002 }
1003 curVals.add(val);
1004 }
1005 }
1006
1007 public interface WifiListener {
1008 /**
1009 * Called when the state of Wifi has changed, the state will be one of
1010 * the following.
1011 *
1012 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
1013 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
1014 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
1015 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
1016 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
1017 * <p>
1018 *
1019 * @param state The new state of wifi.
1020 */
1021 void onWifiStateChanged(int state);
1022
1023 /**
1024 * Called when the connection state of wifi has changed and isConnected
1025 * should be called to get the updated state.
1026 */
1027 void onConnectedChanged();
1028
1029 /**
1030 * Called to indicate the list of AccessPoints has been updated and
1031 * getAccessPoints should be called to get the latest information.
1032 */
1033 void onAccessPointsChanged();
1034 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001035
1036 /**
1037 * Helps capture notifications that were generated during AccessPoint modification. Used later
1038 * on by {@link #copyAndNotifyListeners(boolean)} to send notifications.
1039 */
1040 private static class AccessPointListenerAdapter implements AccessPoint.AccessPointListener {
1041 static final int AP_CHANGED = 1;
1042 static final int LEVEL_CHANGED = 2;
1043
1044 final SparseIntArray mPendingNotifications = new SparseIntArray();
1045
1046 @Override
1047 public void onAccessPointChanged(AccessPoint accessPoint) {
1048 int type = mPendingNotifications.get(accessPoint.mId);
1049 mPendingNotifications.put(accessPoint.mId, type | AP_CHANGED);
1050 }
1051
1052 @Override
1053 public void onLevelChanged(AccessPoint accessPoint) {
1054 int type = mPendingNotifications.get(accessPoint.mId);
1055 mPendingNotifications.put(accessPoint.mId, type | LEVEL_CHANGED);
1056 }
1057 }
1058
1059 /**
1060 * Responsible for copying access points from {@link #mInternalAccessPoints} and notifying
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001061 * accesspoint listeners.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001062 *
1063 * @param notifyListeners if true, accesspoint listeners are notified, otherwise notifications
1064 * dropped.
1065 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001066 @MainThread
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001067 private void copyAndNotifyListeners(boolean notifyListeners) {
1068 // Need to watch out for memory allocations on main thread.
1069 SparseArray<AccessPoint> oldAccessPoints = new SparseArray<>();
1070 SparseIntArray notificationMap = null;
1071 List<AccessPoint> updatedAccessPoints = new ArrayList<>();
1072
1073 for (AccessPoint accessPoint : mAccessPoints) {
1074 oldAccessPoints.put(accessPoint.mId, accessPoint);
1075 }
1076
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -07001077 if (DBG()) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001078 Log.d(TAG, "Starting to copy AP items on the MainHandler");
1079 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001080 synchronized (mLock) {
Sundeep Ghuman221327d2017-05-02 15:58:39 -07001081 if (notifyListeners) {
1082 notificationMap = mAccessPointListenerAdapter.mPendingNotifications.clone();
1083 }
1084
1085 mAccessPointListenerAdapter.mPendingNotifications.clear();
1086
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001087 for (AccessPoint internalAccessPoint : mInternalAccessPoints) {
1088 AccessPoint accessPoint = oldAccessPoints.get(internalAccessPoint.mId);
1089 if (accessPoint == null) {
1090 accessPoint = new AccessPoint(mContext, internalAccessPoint);
1091 } else {
1092 accessPoint.copyFrom(internalAccessPoint);
1093 }
1094 updatedAccessPoints.add(accessPoint);
1095 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001096 }
1097
1098 mAccessPoints.clear();
1099 mAccessPoints.addAll(updatedAccessPoints);
1100
1101 if (notificationMap != null && notificationMap.size() > 0) {
1102 for (AccessPoint accessPoint : updatedAccessPoints) {
1103 int notificationType = notificationMap.get(accessPoint.mId);
1104 AccessPoint.AccessPointListener listener = accessPoint.mAccessPointListener;
1105 if (notificationType == 0 || listener == null) {
1106 continue;
1107 }
1108
1109 if ((notificationType & AccessPointListenerAdapter.AP_CHANGED) != 0) {
1110 listener.onAccessPointChanged(accessPoint);
1111 }
1112
1113 if ((notificationType & AccessPointListenerAdapter.LEVEL_CHANGED) != 0) {
1114 listener.onLevelChanged(accessPoint);
1115 }
1116 }
1117 }
1118 }
Jason Monkd52356a2015-01-28 10:40:41 -05001119}