blob: 7a63b8acc9e1fe8fa70e3bb2b0c7298dbfe6bc76 [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;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -070039import android.os.ConditionVariable;
Jason Monkd52356a2015-01-28 10:40:41 -050040import android.os.Handler;
Jason Monk30d80042015-05-08 16:54:18 -040041import android.os.Looper;
Jason Monkd52356a2015-01-28 10:40:41 -050042import android.os.Message;
Sundeep Ghumane869d832017-01-25 16:23:43 -080043import android.provider.Settings;
Sundeep Ghumance6bab82017-04-19 16:21:46 -070044import android.support.annotation.GuardedBy;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080045import android.util.ArraySet;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070046import android.util.Log;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -070047import android.util.SparseArray;
48import android.util.SparseIntArray;
Jason Monkd52356a2015-01-28 10:40:41 -050049import android.widget.Toast;
50
51import com.android.internal.annotations.VisibleForTesting;
52import com.android.settingslib.R;
53
54import java.io.PrintWriter;
55import java.util.ArrayList;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070056import java.util.Collection;
Jason Monkd52356a2015-01-28 10:40:41 -050057import java.util.Collections;
58import java.util.HashMap;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070059import java.util.Iterator;
Jason Monkd52356a2015-01-28 10:40:41 -050060import java.util.List;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070061import java.util.Map;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080062import java.util.Set;
Jason Monkd52356a2015-01-28 10:40:41 -050063import java.util.concurrent.atomic.AtomicBoolean;
64
65/**
66 * Tracks saved or available wifi networks and their state.
67 */
68public class WifiTracker {
Peter Qiuced37db2017-03-14 15:51:22 -070069 // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080070
Jason Monkd52356a2015-01-28 10:40:41 -050071 private static final String TAG = "WifiTracker";
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080072 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Jason Monkd52356a2015-01-28 10:40:41 -050073
74 /** verbose logging flag. this flag is set thru developer debugging options
75 * and used so as to assist with in-the-field WiFi connectivity debugging */
76 public static int sVerboseLogging = 0;
77
78 // TODO: Allow control of this?
79 // Combo scans can take 5-6s to complete - set to 10s.
80 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -070081 private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
Jason Monkd52356a2015-01-28 10:40:41 -050082
83 private final Context mContext;
84 private final WifiManager mWifiManager;
85 private final IntentFilter mFilter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +090086 private final ConnectivityManager mConnectivityManager;
87 private final NetworkRequest mNetworkRequest;
Jason Monkd52356a2015-01-28 10:40:41 -050088 private final AtomicBoolean mConnected = new AtomicBoolean(false);
89 private final WifiListener mListener;
90 private final boolean mIncludeSaved;
91 private final boolean mIncludeScans;
Vinit Deshpandedcf00c92015-04-15 18:32:09 -070092 private final boolean mIncludePasspoints;
Sundeep Ghuman71f4a822017-04-18 19:51:46 -070093 @VisibleForTesting final MainHandler mMainHandler;
Jason Monk30d80042015-05-08 16:54:18 -040094 private final WorkHandler mWorkHandler;
95
Ajay Nadathurd7b689a2016-08-31 15:07:56 -070096 private WifiTrackerNetworkCallback mNetworkCallback;
97
Amin Shaikha80ae0c2017-03-23 16:18:30 -070098 private int mNumSavedNetworks;
Sundeep Ghumance6bab82017-04-19 16:21:46 -070099
100 @GuardedBy("mLock")
Jason Monkd52356a2015-01-28 10:40:41 -0500101 private boolean mRegistered;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700102
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700103 /**
104 * The externally visible access point list.
105 *
106 * Updated using main handler. Clone of this collection is returned from
107 * {@link #getAccessPoints()}
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700108 */
109 private final List<AccessPoint> mAccessPoints = new ArrayList<>();
110
111 /**
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700112 * The internal list of access points, synchronized on itself.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700113 *
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700114 * Never exposed outside this class.
115 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700116 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700117 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
118
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700119 /**
120 * Synchronization lock for managing concurrency between main and worker threads.
121 *
122 * <p>This lock should be held for all background work.
123 * TODO(b/37674366): Remove the worker thread so synchronization is no longer necessary.
124 */
125 private final Object mLock = new Object();
126
Sundeep Ghuman221327d2017-05-02 15:58:39 -0700127 //visible to both worker and main thread.
128 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700129 private final AccessPointListenerAdapter mAccessPointListenerAdapter
130 = new AccessPointListenerAdapter();
131
132 private final HashMap<String, Integer> mSeenBssids = new HashMap<>();
133 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700134 private Integer mScanId = 0;
Jason Monkd52356a2015-01-28 10:40:41 -0500135
136 private NetworkInfo mLastNetworkInfo;
137 private WifiInfo mLastInfo;
138
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800139 private final NetworkScoreManager mNetworkScoreManager;
140 private final WifiNetworkScoreCache mScoreCache;
Sundeep Ghumane869d832017-01-25 16:23:43 -0800141 private boolean mNetworkScoringUiEnabled;
142 private final ContentObserver mObserver;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800143
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700144 @GuardedBy("mLock")
145 private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
146
Jason Monkd52356a2015-01-28 10:40:41 -0500147 @VisibleForTesting
148 Scanner mScanner;
149
Jason Monk30d80042015-05-08 16:54:18 -0400150 public WifiTracker(Context context, WifiListener wifiListener,
151 boolean includeSaved, boolean includeScans) {
152 this(context, wifiListener, null, includeSaved, includeScans);
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700153 }
Jason Monk30d80042015-05-08 16:54:18 -0400154
155 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
156 boolean includeSaved, boolean includeScans) {
157 this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
158 }
159
160 public WifiTracker(Context context, WifiListener wifiListener,
161 boolean includeSaved, boolean includeScans, boolean includePasspoints) {
162 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
163 }
164
165 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
166 boolean includeSaved, boolean includeScans, boolean includePasspoints) {
167 this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900168 context.getSystemService(WifiManager.class),
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800169 context.getSystemService(ConnectivityManager.class),
170 context.getSystemService(NetworkScoreManager.class), Looper.myLooper()
171 );
Jason Monkd52356a2015-01-28 10:40:41 -0500172 }
173
174 @VisibleForTesting
Jason Monk30d80042015-05-08 16:54:18 -0400175 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
176 boolean includeSaved, boolean includeScans, boolean includePasspoints,
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900177 WifiManager wifiManager, ConnectivityManager connectivityManager,
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800178 NetworkScoreManager networkScoreManager, Looper currentLooper) {
Jason Monkd52356a2015-01-28 10:40:41 -0500179 if (!includeSaved && !includeScans) {
180 throw new IllegalArgumentException("Must include either saved or scans");
181 }
182 mContext = context;
Jason Monkbf3d1af2015-05-22 11:25:04 -0400183 if (currentLooper == null) {
184 // When we aren't on a looper thread, default to the main.
185 currentLooper = Looper.getMainLooper();
186 }
Jason Monk2b51cc32015-05-13 11:07:53 -0400187 mMainHandler = new MainHandler(currentLooper);
Jason Monk30d80042015-05-08 16:54:18 -0400188 mWorkHandler = new WorkHandler(
Jason Monk2b51cc32015-05-13 11:07:53 -0400189 workerLooper != null ? workerLooper : currentLooper);
Jason Monkd52356a2015-01-28 10:40:41 -0500190 mWifiManager = wifiManager;
191 mIncludeSaved = includeSaved;
192 mIncludeScans = includeScans;
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700193 mIncludePasspoints = includePasspoints;
Jason Monkd52356a2015-01-28 10:40:41 -0500194 mListener = wifiListener;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900195 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500196
197 // check if verbose logging has been turned on or off
198 sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
199
200 mFilter = new IntentFilter();
201 mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
202 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
203 mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
204 mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
205 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
206 mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
207 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700208 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900209
210 mNetworkRequest = new NetworkRequest.Builder()
211 .clearCapabilities()
212 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
213 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800214
215 mNetworkScoreManager = networkScoreManager;
216
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700217 mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800218 @Override
219 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700220 synchronized (mLock) {
221 if (!mRegistered) return;
222 }
223
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800224 if (Log.isLoggable(TAG, Log.VERBOSE)) {
225 Log.v(TAG, "Score cache was updated with networks: " + networks);
226 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700227 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800228 }
229 });
Sundeep Ghumane869d832017-01-25 16:23:43 -0800230
231 mObserver = new ContentObserver(mWorkHandler) {
232 @Override
233 public void onChange(boolean selfChange) {
234 mNetworkScoringUiEnabled =
235 Settings.Global.getInt(
236 mContext.getContentResolver(),
237 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
238 }
239 };
Jason Monkd52356a2015-01-28 10:40:41 -0500240 }
241
242 /**
Sundeep Ghuman9c0b97e2017-04-13 16:05:46 -0700243 * Synchronously update the list of access points with the latest information.
Jason Monkd52356a2015-01-28 10:40:41 -0500244 */
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());
251 updateAccessPointsLocked();
Sundeep Ghuman9c0b97e2017-04-13 16:05:46 -0700252
Sundeep Ghuman047b2992017-05-10 17:45:13 -0700253 if (DBG) {
254 Log.d(TAG, "force update - internal access point list:\n" + mInternalAccessPoints);
255 }
256
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700257 // Synchronously copy access points
258 mMainHandler.removeMessages(MainHandler.MSG_ACCESS_POINT_CHANGED);
259 mMainHandler.handleMessage(
260 Message.obtain(mMainHandler, MainHandler.MSG_ACCESS_POINT_CHANGED));
Sundeep Ghuman047b2992017-05-10 17:45:13 -0700261 if (DBG) {
262 Log.d(TAG, "force update - external access point list:\n" + mAccessPoints);
263 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700264 }
Jason Monkd52356a2015-01-28 10:40:41 -0500265 }
266
267 /**
268 * Force a scan for wifi networks to happen now.
269 */
270 public void forceScan() {
271 if (mWifiManager.isWifiEnabled() && mScanner != null) {
272 mScanner.forceScan();
273 }
274 }
275
276 /**
277 * Temporarily stop scanning for wifi networks.
278 */
279 public void pauseScanning() {
280 if (mScanner != null) {
281 mScanner.pause();
Jason Monk6572eae2015-02-05 10:34:20 -0500282 mScanner = null;
Jason Monkd52356a2015-01-28 10:40:41 -0500283 }
284 }
285
286 /**
287 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800288 *
289 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500290 */
291 public void resumeScanning() {
Jason Monk6572eae2015-02-05 10:34:20 -0500292 if (mScanner == null) {
293 mScanner = new Scanner();
294 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700295
Jason Monkdbd05a92015-07-06 12:55:48 -0400296 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
Jason Monkd52356a2015-01-28 10:40:41 -0500297 if (mWifiManager.isWifiEnabled()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500298 mScanner.resume();
299 }
Jason Monkd52356a2015-01-28 10:40:41 -0500300 }
301
302 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800303 * Start tracking wifi networks and scores.
304 *
305 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500306 * then forceUpdate() must be called to populate getAccessPoints().
307 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700308 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500309 public void startTracking() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700310 synchronized (mLock) {
311 registerScoreCache();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800312
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700313 mContext.getContentResolver().registerContentObserver(
314 Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
315 false /* notifyForDescendants */,
316 mObserver);
317 mObserver.onChange(false /* selfChange */); // Set mScoringUiEnabled
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800318
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700319 resumeScanning();
320 if (!mRegistered) {
321 mContext.registerReceiver(mReceiver, mFilter);
322 // NetworkCallback objects cannot be reused. http://b/20701525 .
323 mNetworkCallback = new WifiTrackerNetworkCallback();
324 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
325 mRegistered = true;
326 }
Jason Monkd52356a2015-01-28 10:40:41 -0500327 }
328 }
329
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800330 private void registerScoreCache() {
331 mNetworkScoreManager.registerNetworkScoreCache(
332 NetworkKey.TYPE_WIFI,
333 mScoreCache,
334 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
335 }
336
337 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
338 if (keys.isEmpty()) return;
339
340 if (DBG) {
341 Log.d(TAG, "Requesting scores for Network Keys: " + keys);
342 }
343 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700344 synchronized (mLock) {
345 mRequestedScores.addAll(keys);
346 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800347 }
348
Jason Monkd52356a2015-01-28 10:40:41 -0500349 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800350 * Stop tracking wifi networks and scores.
351 *
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700352 * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
353 * ensure proper cleanup and prevent any further callbacks from occuring.
Jason Monkd52356a2015-01-28 10:40:41 -0500354 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700355 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500356 public void stopTracking() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700357 synchronized (mLock) {
358 if (mRegistered) {
359 mContext.unregisterReceiver(mReceiver);
360 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
361 mRegistered = false;
362 }
363 unregisterAndClearScoreCache();
364 pauseScanning();
365 mContext.getContentResolver().unregisterContentObserver(mObserver);
366
367 mWorkHandler.removePendingMessages();
368 mMainHandler.removePendingMessages();
Jason Monkd52356a2015-01-28 10:40:41 -0500369 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800370 }
371
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800372 private void unregisterAndClearScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800373 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
374 mScoreCache.clearScores();
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800375
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700376 // Synchronize on mLock to avoid concurrent modification during updateAccessPointsLocked
377 synchronized (mLock) {
378 mRequestedScores.clear();
379 }
Jason Monkd52356a2015-01-28 10:40:41 -0500380 }
381
382 /**
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700383 * Gets the current list of access points. Should be called from main thread, otherwise
384 * expect inconsistencies
Jason Monkd52356a2015-01-28 10:40:41 -0500385 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700386 @MainThread
Jason Monkd52356a2015-01-28 10:40:41 -0500387 public List<AccessPoint> getAccessPoints() {
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700388 return new ArrayList<>(mAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500389 }
390
391 public WifiManager getManager() {
392 return mWifiManager;
393 }
394
395 public boolean isWifiEnabled() {
396 return mWifiManager.isWifiEnabled();
397 }
398
399 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700400 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
401 * is tracking saved networks.
Jason Monkd52356a2015-01-28 10:40:41 -0500402 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700403 public int getNumSavedNetworks() {
404 return mNumSavedNetworks;
Jason Monkd52356a2015-01-28 10:40:41 -0500405 }
406
407 public boolean isConnected() {
408 return mConnected.get();
409 }
410
411 public void dump(PrintWriter pw) {
412 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400413 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500414 pw.println(" " + accessPoint);
415 }
416 }
417
Jason Monkdbd05a92015-07-06 12:55:48 -0400418 private void handleResume() {
419 mScanResultCache.clear();
420 mSeenBssids.clear();
421 mScanId = 0;
422 }
423
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700424 private Collection<ScanResult> fetchScanResults() {
425 mScanId++;
426 final List<ScanResult> newResults = mWifiManager.getScanResults();
427 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700428 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
429 continue;
430 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700431 mScanResultCache.put(newResult.BSSID, newResult);
432 mSeenBssids.put(newResult.BSSID, mScanId);
433 }
434
435 if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
436 if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
437 Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
438 for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
439 it.hasNext(); /* nothing */) {
440 Map.Entry<String, Integer> e = it.next();
441 if (e.getValue() < threshold) {
442 ScanResult result = mScanResultCache.get(e.getKey());
443 if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
444 mScanResultCache.remove(e.getKey());
445 it.remove();
446 }
447 }
448 if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
449 }
450
451 return mScanResultCache.values();
452 }
453
Mitchell Wills5a42db22015-08-03 09:46:08 -0700454 private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) {
455 final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
456 if (configs != null) {
457 for (WifiConfiguration config : configs) {
458 if (mLastInfo != null && networkId == config.networkId &&
459 !(config.selfAdded && config.numAssociation == 0)) {
460 return config;
461 }
462 }
463 }
464 return null;
465 }
466
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700467 /** Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first. */
468 private void updateAccessPointsLocked() {
469 synchronized (mLock) {
470 updateAccessPoints();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700471 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700472 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700473
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700474 /**
475 * Update the internal list of access points.
476 *
477 * <p>Should never be called directly, use {@link #updateAccessPointsLocked()} instead.
478 */
479 @GuardedBy("mLock")
480 private void updateAccessPoints() {
Jason Monkd52356a2015-01-28 10:40:41 -0500481 // Swap the current access points into a cached list.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700482 List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monk30d80042015-05-08 16:54:18 -0400483 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
484
Jason Monkd52356a2015-01-28 10:40:41 -0500485 // Clear out the configs so we don't think something is saved when it isn't.
Jason Monk30d80042015-05-08 16:54:18 -0400486 for (AccessPoint accessPoint : cachedAccessPoints) {
Jason Monkd52356a2015-01-28 10:40:41 -0500487 accessPoint.clearConfig();
488 }
489
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700490 /* Lookup table to more quickly update AccessPoints by only considering objects with the
491 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
Jason Monkd52356a2015-01-28 10:40:41 -0500492 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
Vinit Deshpandefc406002015-04-15 18:10:55 -0700493 WifiConfiguration connectionConfig = null;
Mitchell Wills5a42db22015-08-03 09:46:08 -0700494 if (mLastInfo != null) {
495 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
496 }
Jason Monkd52356a2015-01-28 10:40:41 -0500497
Sanket Padawe0775a982015-08-19 14:57:46 -0700498 final Collection<ScanResult> results = fetchScanResults();
499
Jason Monkd52356a2015-01-28 10:40:41 -0500500 final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
501 if (configs != null) {
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700502 mNumSavedNetworks = 0;
Jason Monkd52356a2015-01-28 10:40:41 -0500503 for (WifiConfiguration config : configs) {
504 if (config.selfAdded && config.numAssociation == 0) {
505 continue;
506 }
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700507 mNumSavedNetworks++;
Jason Monk30d80042015-05-08 16:54:18 -0400508 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500509 if (mLastInfo != null && mLastNetworkInfo != null) {
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800510 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Jason Monkd52356a2015-01-28 10:40:41 -0500511 }
512 if (mIncludeSaved) {
Sundeep Ghumandc6cf4b2017-03-08 16:18:29 -0800513 // If saved network not present in scan result then set its Rssi to
514 // UNREACHABLE_RSSI
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800515 boolean apFound = false;
516 for (ScanResult result : results) {
517 if (result.SSID.equals(accessPoint.getSsidStr())) {
518 apFound = true;
519 break;
Sanket Padawe0775a982015-08-19 14:57:46 -0700520 }
Sanket Padawe0775a982015-08-19 14:57:46 -0700521 }
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800522 if (!apFound) {
Sundeep Ghuman54bdcfa2017-03-08 19:52:05 -0800523 accessPoint.setUnreachable();
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700524 }
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800525 accessPoints.add(accessPoint);
526 apMap.put(accessPoint.getSsidStr(), accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500527 } else {
528 // If we aren't using saved networks, drop them into the cache so that
529 // we have access to their saved info.
Jason Monk30d80042015-05-08 16:54:18 -0400530 cachedAccessPoints.add(accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500531 }
532 }
533 }
534
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800535 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500536 if (results != null) {
537 for (ScanResult result : results) {
538 // Ignore hidden and ad-hoc networks.
539 if (result.SSID == null || result.SSID.length() == 0 ||
540 result.capabilities.contains("[IBSS]")) {
541 continue;
542 }
543
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800544 NetworkKey key = NetworkKey.createFromScanResult(result);
Stephen Chenfde900d2017-02-14 16:40:21 -0800545 if (key != null && !mRequestedScores.contains(key)) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800546 scoresToRequest.add(key);
547 }
548
Jason Monkd52356a2015-01-28 10:40:41 -0500549 boolean found = false;
550 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
551 if (accessPoint.update(result)) {
552 found = true;
553 break;
554 }
555 }
556 if (!found && mIncludeScans) {
Jason Monk30d80042015-05-08 16:54:18 -0400557 AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500558 if (mLastInfo != null && mLastNetworkInfo != null) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700559 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
Jason Monkd52356a2015-01-28 10:40:41 -0500560 }
Vinit Deshpandefc406002015-04-15 18:10:55 -0700561
Vinit Deshpandea0d929e2015-06-12 15:18:44 -0700562 if (result.isPasspointNetwork()) {
Peter Qiu2c3b5ee22017-02-01 11:49:15 -0800563 // Retrieve a WifiConfiguration for a Passpoint provider that matches
564 // the given ScanResult. This is used for showing that a given AP
565 // (ScanResult) is available via a Passpoint provider (provider friendly
566 // name).
Peter Qiu0ffb89d2017-03-27 13:45:25 -0700567 try {
568 WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
569 if (config != null) {
570 accessPoint.update(config);
571 }
572 } catch (UnsupportedOperationException e) {
573 // Passpoint not supported on the device.
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700574 }
575 }
576
Jason Monk30d80042015-05-08 16:54:18 -0400577 accessPoints.add(accessPoint);
Jason Monk6980d122015-06-15 10:07:55 -0400578 apMap.put(accessPoint.getSsidStr(), accessPoint);
Jason Monkd52356a2015-01-28 10:40:41 -0500579 }
580 }
581 }
582
Stephen Chen21f68682017-04-04 13:23:31 -0700583 requestScoresForNetworkKeys(scoresToRequest);
584 for (AccessPoint ap : accessPoints) {
585 ap.update(mScoreCache, mNetworkScoringUiEnabled);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800586 }
587
Jason Monkd52356a2015-01-28 10:40:41 -0500588 // Pre-sort accessPoints to speed preference insertion
Jason Monk30d80042015-05-08 16:54:18 -0400589 Collections.sort(accessPoints);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700590
591 // Log accesspoints that were deleted
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800592 if (DBG) {
593 Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
594 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
595 if (prevAccessPoint.getSsid() == null)
596 continue;
597 String prevSsid = prevAccessPoint.getSsidStr();
598 boolean found = false;
599 for (AccessPoint newAccessPoint : accessPoints) {
600 if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid()
601 .equals(prevSsid)) {
602 found = true;
603 break;
604 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700605 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800606 if (!found)
607 Log.d(TAG, "Did not find " + prevSsid + " in this scan");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700608 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800609 Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700610 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700611
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700612 mInternalAccessPoints.clear();
613 mInternalAccessPoints.addAll(accessPoints);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700614
615 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
Jason Monkd52356a2015-01-28 10:40:41 -0500616 }
617
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700618 @VisibleForTesting
619 AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400620 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500621 for (int i = 0; i < N; i++) {
Jason Monk30d80042015-05-08 16:54:18 -0400622 if (cache.get(i).matches(result)) {
623 AccessPoint ret = cache.remove(i);
Jason Monkd52356a2015-01-28 10:40:41 -0500624 ret.update(result);
625 return ret;
626 }
627 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700628 final AccessPoint accessPoint = new AccessPoint(mContext, result);
629 accessPoint.setListener(mAccessPointListenerAdapter);
630 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500631 }
632
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700633 @VisibleForTesting
634 AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400635 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500636 for (int i = 0; i < N; i++) {
Jason Monk30d80042015-05-08 16:54:18 -0400637 if (cache.get(i).matches(config)) {
638 AccessPoint ret = cache.remove(i);
Jason Monkd52356a2015-01-28 10:40:41 -0500639 ret.loadConfig(config);
640 return ret;
641 }
642 }
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700643 final AccessPoint accessPoint = new AccessPoint(mContext, config);
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700644 accessPoint.setListener(mAccessPointListenerAdapter);
645 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500646 }
647
648 private void updateNetworkInfo(NetworkInfo networkInfo) {
Jason Monke04ae8a2015-06-03 14:06:34 -0400649 /* sticky broadcasts can call this when wifi is disabled */
650 if (!mWifiManager.isWifiEnabled()) {
651 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
652 return;
653 }
Jason Monkd52356a2015-01-28 10:40:41 -0500654
Jason Monke04ae8a2015-06-03 14:06:34 -0400655 if (networkInfo != null &&
656 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
657 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
658 } else {
659 mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
Jason Monkd52356a2015-01-28 10:40:41 -0500660 }
661
Jason Monkd52356a2015-01-28 10:40:41 -0500662 if (networkInfo != null) {
663 mLastNetworkInfo = networkInfo;
664 }
665
Mitchell Wills5a42db22015-08-03 09:46:08 -0700666 WifiConfiguration connectionConfig = null;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900667 mLastInfo = mWifiManager.getConnectionInfo();
Mitchell Wills5a42db22015-08-03 09:46:08 -0700668 if (mLastInfo != null) {
669 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
670 }
671
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700672 boolean updated = false;
673 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700674
675 synchronized (mLock) {
676 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
677 AccessPoint ap = mInternalAccessPoints.get(i);
678 boolean previouslyConnected = ap.isActive();
679 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
680 updated = true;
681 if (previouslyConnected != ap.isActive()) reorder = true;
682 }
683 if (ap.update(mScoreCache, mNetworkScoringUiEnabled)) {
684 reorder = true;
685 updated = true;
686 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800687 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700688
689 if (reorder) Collections.sort(mInternalAccessPoints);
690
691 if (updated) mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800692 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800693 }
694
695 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700696 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800697 *
698 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700699 *
700 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800701 */
702 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700703 synchronized (mLock) {
704 boolean updated = false;
705 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
706 if (mInternalAccessPoints.get(i).update(mScoreCache, mNetworkScoringUiEnabled)) {
707 updated = true;
708 }
Jason Monkd52356a2015-01-28 10:40:41 -0500709 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700710 if (updated) {
711 Collections.sort(mInternalAccessPoints);
712 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
713 }
Jason Monkd52356a2015-01-28 10:40:41 -0500714 }
715 }
716
717 private void updateWifiState(int state) {
Mitchell Willscf0875a2016-04-18 14:54:53 -0700718 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
Jason Monkd52356a2015-01-28 10:40:41 -0500719 }
720
721 public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
Vinit Deshpandedcf00c92015-04-15 18:32:09 -0700722 boolean includeScans, boolean includePasspoints) {
723 WifiTracker tracker = new WifiTracker(context,
Jason Monk30d80042015-05-08 16:54:18 -0400724 null, null, includeSaved, includeScans, includePasspoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500725 tracker.forceUpdate();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700726 tracker.copyAndNotifyListeners(false /*notifyListeners*/);
Jason Monkd52356a2015-01-28 10:40:41 -0500727 return tracker.getAccessPoints();
728 }
729
730 @VisibleForTesting
731 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
732 @Override
733 public void onReceive(Context context, Intent intent) {
734 String action = intent.getAction();
735 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
736 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
737 WifiManager.WIFI_STATE_UNKNOWN));
738 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
739 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
740 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
Jason Monk30d80042015-05-08 16:54:18 -0400741 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Jason Monkd52356a2015-01-28 10:40:41 -0500742 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
743 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
744 WifiManager.EXTRA_NETWORK_INFO);
745 mConnected.set(info.isConnected());
Jason Monk30d80042015-05-08 16:54:18 -0400746
747 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
748
Jason Monk30d80042015-05-08 16:54:18 -0400749 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
750 .sendToTarget();
Sundeep Ghuman2b489902017-02-22 18:17:29 -0800751 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700752 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700753 NetworkInfo info =
754 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
755 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
756 .sendToTarget();
Jason Monkd52356a2015-01-28 10:40:41 -0500757 }
758 }
759 };
760
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900761 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
762 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
763 if (network.equals(mWifiManager.getCurrentNetwork())) {
764 // We don't send a NetworkInfo object along with this message, because even if we
765 // fetch one from ConnectivityManager, it might be older than the most recent
766 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
767 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
768 }
769 }
770 }
771
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700772 @VisibleForTesting
773 final class MainHandler extends Handler {
774 @VisibleForTesting static final int MSG_CONNECTED_CHANGED = 0;
775 @VisibleForTesting static final int MSG_WIFI_STATE_CHANGED = 1;
776 @VisibleForTesting static final int MSG_ACCESS_POINT_CHANGED = 2;
Jason Monke04ae8a2015-06-03 14:06:34 -0400777 private static final int MSG_RESUME_SCANNING = 3;
778 private static final int MSG_PAUSE_SCANNING = 4;
Jason Monk30d80042015-05-08 16:54:18 -0400779
Jason Monk2b51cc32015-05-13 11:07:53 -0400780 public MainHandler(Looper looper) {
781 super(looper);
782 }
783
Jason Monk30d80042015-05-08 16:54:18 -0400784 @Override
785 public void handleMessage(Message msg) {
786 if (mListener == null) {
787 return;
788 }
789 switch (msg.what) {
790 case MSG_CONNECTED_CHANGED:
791 mListener.onConnectedChanged();
792 break;
793 case MSG_WIFI_STATE_CHANGED:
794 mListener.onWifiStateChanged(msg.arg1);
795 break;
796 case MSG_ACCESS_POINT_CHANGED:
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700797 copyAndNotifyListeners(true /*notifyListeners*/);
Jason Monk30d80042015-05-08 16:54:18 -0400798 mListener.onAccessPointsChanged();
799 break;
Jason Monke04ae8a2015-06-03 14:06:34 -0400800 case MSG_RESUME_SCANNING:
801 if (mScanner != null) {
802 mScanner.resume();
803 }
804 break;
805 case MSG_PAUSE_SCANNING:
806 if (mScanner != null) {
807 mScanner.pause();
808 }
809 break;
Jason Monk30d80042015-05-08 16:54:18 -0400810 }
811 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700812
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700813 void removePendingMessages() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700814 removeMessages(MSG_ACCESS_POINT_CHANGED);
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700815 removeMessages(MSG_CONNECTED_CHANGED);
816 removeMessages(MSG_WIFI_STATE_CHANGED);
817 removeMessages(MSG_PAUSE_SCANNING);
818 removeMessages(MSG_RESUME_SCANNING);
819 }
Jason Monk30d80042015-05-08 16:54:18 -0400820 }
821
822 private final class WorkHandler extends Handler {
823 private static final int MSG_UPDATE_ACCESS_POINTS = 0;
824 private static final int MSG_UPDATE_NETWORK_INFO = 1;
Jason Monkdbd05a92015-07-06 12:55:48 -0400825 private static final int MSG_RESUME = 2;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700826 private static final int MSG_UPDATE_WIFI_STATE = 3;
Jason Monk30d80042015-05-08 16:54:18 -0400827
828 public WorkHandler(Looper looper) {
829 super(looper);
830 }
831
832 @Override
833 public void handleMessage(Message msg) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700834 synchronized (mLock) {
835 processMessage(msg);
836 }
837 }
838
839 @GuardedBy("mLock")
840 private void processMessage(Message msg) {
841 if (!mRegistered) return;
842
Jason Monk30d80042015-05-08 16:54:18 -0400843 switch (msg.what) {
844 case MSG_UPDATE_ACCESS_POINTS:
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700845 updateAccessPointsLocked();
Jason Monk30d80042015-05-08 16:54:18 -0400846 break;
847 case MSG_UPDATE_NETWORK_INFO:
848 updateNetworkInfo((NetworkInfo) msg.obj);
849 break;
Jason Monkdbd05a92015-07-06 12:55:48 -0400850 case MSG_RESUME:
851 handleResume();
852 break;
Mitchell Willscf0875a2016-04-18 14:54:53 -0700853 case MSG_UPDATE_WIFI_STATE:
854 if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
855 if (mScanner != null) {
856 // We only need to resume if mScanner isn't null because
857 // that means we want to be scanning.
858 mScanner.resume();
859 }
860 } else {
861 mLastInfo = null;
862 mLastNetworkInfo = null;
863 if (mScanner != null) {
864 mScanner.pause();
865 }
866 }
867 mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
868 .sendToTarget();
869 break;
Jason Monk30d80042015-05-08 16:54:18 -0400870 }
871 }
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700872
873 private void removePendingMessages() {
874 removeMessages(MSG_UPDATE_ACCESS_POINTS);
875 removeMessages(MSG_UPDATE_NETWORK_INFO);
876 removeMessages(MSG_RESUME);
877 removeMessages(MSG_UPDATE_WIFI_STATE);
Sundeep Ghuman71f4a822017-04-18 19:51:46 -0700878 }
Jason Monk30d80042015-05-08 16:54:18 -0400879 }
880
Jason Monkd52356a2015-01-28 10:40:41 -0500881 @VisibleForTesting
882 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400883 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500884
Jason Monkd52356a2015-01-28 10:40:41 -0500885 private int mRetry = 0;
886
887 void resume() {
Jason Monk6572eae2015-02-05 10:34:20 -0500888 if (!hasMessages(MSG_SCAN)) {
889 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500890 }
891 }
892
893 void forceScan() {
Jason Monk6572eae2015-02-05 10:34:20 -0500894 removeMessages(MSG_SCAN);
895 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500896 }
897
898 void pause() {
899 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500900 removeMessages(MSG_SCAN);
901 }
902
903 @VisibleForTesting
904 boolean isScanning() {
905 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500906 }
907
908 @Override
909 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500910 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500911 if (mWifiManager.startScan()) {
912 mRetry = 0;
913 } else if (++mRetry >= 3) {
914 mRetry = 0;
915 if (mContext != null) {
916 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
917 }
918 return;
919 }
920 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
921 }
922 }
923
924 /** A restricted multimap for use in constructAccessPoints */
925 private static class Multimap<K,V> {
926 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
927 /** retrieve a non-null list of values with key K */
928 List<V> getAll(K key) {
929 List<V> values = store.get(key);
930 return values != null ? values : Collections.<V>emptyList();
931 }
932
933 void put(K key, V val) {
934 List<V> curVals = store.get(key);
935 if (curVals == null) {
936 curVals = new ArrayList<V>(3);
937 store.put(key, curVals);
938 }
939 curVals.add(val);
940 }
941 }
942
943 public interface WifiListener {
944 /**
945 * Called when the state of Wifi has changed, the state will be one of
946 * the following.
947 *
948 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
949 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
950 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
951 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
952 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
953 * <p>
954 *
955 * @param state The new state of wifi.
956 */
957 void onWifiStateChanged(int state);
958
959 /**
960 * Called when the connection state of wifi has changed and isConnected
961 * should be called to get the updated state.
962 */
963 void onConnectedChanged();
964
965 /**
966 * Called to indicate the list of AccessPoints has been updated and
967 * getAccessPoints should be called to get the latest information.
968 */
969 void onAccessPointsChanged();
970 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700971
972 /**
973 * Helps capture notifications that were generated during AccessPoint modification. Used later
974 * on by {@link #copyAndNotifyListeners(boolean)} to send notifications.
975 */
976 private static class AccessPointListenerAdapter implements AccessPoint.AccessPointListener {
977 static final int AP_CHANGED = 1;
978 static final int LEVEL_CHANGED = 2;
979
980 final SparseIntArray mPendingNotifications = new SparseIntArray();
981
982 @Override
983 public void onAccessPointChanged(AccessPoint accessPoint) {
984 int type = mPendingNotifications.get(accessPoint.mId);
985 mPendingNotifications.put(accessPoint.mId, type | AP_CHANGED);
986 }
987
988 @Override
989 public void onLevelChanged(AccessPoint accessPoint) {
990 int type = mPendingNotifications.get(accessPoint.mId);
991 mPendingNotifications.put(accessPoint.mId, type | LEVEL_CHANGED);
992 }
993 }
994
995 /**
996 * Responsible for copying access points from {@link #mInternalAccessPoints} and notifying
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700997 * accesspoint listeners.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700998 *
999 * @param notifyListeners if true, accesspoint listeners are notified, otherwise notifications
1000 * dropped.
1001 */
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001002 @MainThread
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001003 private void copyAndNotifyListeners(boolean notifyListeners) {
1004 // Need to watch out for memory allocations on main thread.
1005 SparseArray<AccessPoint> oldAccessPoints = new SparseArray<>();
1006 SparseIntArray notificationMap = null;
1007 List<AccessPoint> updatedAccessPoints = new ArrayList<>();
1008
1009 for (AccessPoint accessPoint : mAccessPoints) {
1010 oldAccessPoints.put(accessPoint.mId, accessPoint);
1011 }
1012
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001013 if (DBG) {
1014 Log.d(TAG, "Starting to copy AP items on the MainHandler");
1015 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -07001016 synchronized (mLock) {
Sundeep Ghuman221327d2017-05-02 15:58:39 -07001017 if (notifyListeners) {
1018 notificationMap = mAccessPointListenerAdapter.mPendingNotifications.clone();
1019 }
1020
1021 mAccessPointListenerAdapter.mPendingNotifications.clear();
1022
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001023 for (AccessPoint internalAccessPoint : mInternalAccessPoints) {
1024 AccessPoint accessPoint = oldAccessPoints.get(internalAccessPoint.mId);
1025 if (accessPoint == null) {
1026 accessPoint = new AccessPoint(mContext, internalAccessPoint);
1027 } else {
1028 accessPoint.copyFrom(internalAccessPoint);
1029 }
1030 updatedAccessPoints.add(accessPoint);
1031 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -07001032 }
1033
1034 mAccessPoints.clear();
1035 mAccessPoints.addAll(updatedAccessPoints);
1036
1037 if (notificationMap != null && notificationMap.size() > 0) {
1038 for (AccessPoint accessPoint : updatedAccessPoints) {
1039 int notificationType = notificationMap.get(accessPoint.mId);
1040 AccessPoint.AccessPointListener listener = accessPoint.mAccessPointListener;
1041 if (notificationType == 0 || listener == null) {
1042 continue;
1043 }
1044
1045 if ((notificationType & AccessPointListenerAdapter.AP_CHANGED) != 0) {
1046 listener.onAccessPointChanged(accessPoint);
1047 }
1048
1049 if ((notificationType & AccessPointListenerAdapter.LEVEL_CHANGED) != 0) {
1050 listener.onLevelChanged(accessPoint);
1051 }
1052 }
1053 }
1054 }
Jason Monkd52356a2015-01-28 10:40:41 -05001055}