blob: df1a7a84e07e91245b256ef3a41f70e60b90b86d [file] [log] [blame]
Jason Monkd52356a2015-01-28 10:40:41 -05001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.settingslib.wifi;
17
Sundeep Ghumanbb399912018-01-29 18:31:15 -080018import android.annotation.AnyThread;
Sundeep Ghumance6bab82017-04-19 16:21:46 -070019import android.annotation.MainThread;
Jason Monkd52356a2015-01-28 10:40:41 -050020import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +090024import android.net.ConnectivityManager;
25import android.net.Network;
26import android.net.NetworkCapabilities;
Jason Monkd52356a2015-01-28 10:40:41 -050027import android.net.NetworkInfo;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080028import android.net.NetworkKey;
Lorenzo Colittiab313f82015-11-26 16:55:15 +090029import android.net.NetworkRequest;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080030import android.net.NetworkScoreManager;
31import android.net.ScoredNetwork;
Jason Monkd52356a2015-01-28 10:40:41 -050032import android.net.wifi.ScanResult;
33import android.net.wifi.WifiConfiguration;
34import android.net.wifi.WifiInfo;
35import android.net.wifi.WifiManager;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080036import android.net.wifi.WifiNetworkScoreCache;
37import android.net.wifi.WifiNetworkScoreCache.CacheListener;
Jason Monkd52356a2015-01-28 10:40:41 -050038import android.os.Handler;
Tony Mantler0edf09b2017-09-28 15:03:37 -070039import android.os.HandlerThread;
Jason Monkd52356a2015-01-28 10:40:41 -050040import android.os.Message;
Tony Mantler0edf09b2017-09-28 15:03:37 -070041import android.os.Process;
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080042import android.os.SystemClock;
Sundeep Ghumane869d832017-01-25 16:23:43 -080043import android.provider.Settings;
Sundeep Ghumance6bab82017-04-19 16:21:46 -070044import android.support.annotation.GuardedBy;
Tony Mantler0edf09b2017-09-28 15:03:37 -070045import android.support.annotation.NonNull;
46import android.support.annotation.VisibleForTesting;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -070047import android.text.format.DateUtils;
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080048import android.util.ArrayMap;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080049import android.util.ArraySet;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070050import android.util.Log;
Jason Monkd52356a2015-01-28 10:40:41 -050051import android.widget.Toast;
52
Jason Monkd52356a2015-01-28 10:40:41 -050053import com.android.settingslib.R;
Tony Mantler0edf09b2017-09-28 15:03:37 -070054import com.android.settingslib.core.lifecycle.Lifecycle;
55import com.android.settingslib.core.lifecycle.LifecycleObserver;
56import com.android.settingslib.core.lifecycle.events.OnDestroy;
57import com.android.settingslib.core.lifecycle.events.OnStart;
58import com.android.settingslib.core.lifecycle.events.OnStop;
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -080059import com.android.settingslib.utils.ThreadUtils;
Jason Monkd52356a2015-01-28 10:40:41 -050060
61import java.io.PrintWriter;
62import java.util.ArrayList;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070063import java.util.Collection;
Jason Monkd52356a2015-01-28 10:40:41 -050064import java.util.Collections;
65import java.util.HashMap;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070066import java.util.Iterator;
Jason Monkd52356a2015-01-28 10:40:41 -050067import java.util.List;
Vinit Deshpandefcd46122015-06-11 18:22:23 -070068import java.util.Map;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080069import java.util.Set;
Jason Monkd52356a2015-01-28 10:40:41 -050070import java.util.concurrent.atomic.AtomicBoolean;
71
72/**
73 * Tracks saved or available wifi networks and their state.
74 */
Tony Mantler0edf09b2017-09-28 15:03:37 -070075public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -070076 /**
77 * Default maximum age in millis of cached scored networks in
78 * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
79 */
Sundeep Ghuman64bf0902017-09-11 13:00:53 -070080 private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -080081
Sundeep Ghuman04f7f342018-01-23 19:18:31 -080082 /** Maximum age of scan results to hold onto while actively scanning. **/
83 private static final long MAX_SCAN_RESULT_AGE_MILLIS = 25000;
84
Jason Monkd52356a2015-01-28 10:40:41 -050085 private static final String TAG = "WifiTracker";
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -070086 private static final boolean DBG() {
87 return Log.isLoggable(TAG, Log.DEBUG);
88 }
Jason Monkd52356a2015-01-28 10:40:41 -050089
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -080090 private static boolean isVerboseLoggingEnabled() {
91 return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
92 }
93
94 /**
95 * Verbose logging flag set thru developer debugging options and used so as to assist with
96 * in-the-field WiFi connectivity debugging.
97 *
98 * <p>{@link #isVerboseLoggingEnabled()} should be read rather than referencing this value
99 * directly, to ensure adb TAG level verbose settings are respected.
100 */
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700101 public static boolean sVerboseLogging;
Jason Monkd52356a2015-01-28 10:40:41 -0500102
103 // TODO: Allow control of this?
104 // Combo scans can take 5-6s to complete - set to 10s.
105 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
106
107 private final Context mContext;
108 private final WifiManager mWifiManager;
109 private final IntentFilter mFilter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900110 private final ConnectivityManager mConnectivityManager;
111 private final NetworkRequest mNetworkRequest;
Jason Monkd52356a2015-01-28 10:40:41 -0500112 private final AtomicBoolean mConnected = new AtomicBoolean(false);
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800113 private final WifiListenerExecutor mListener;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800114 @VisibleForTesting Handler mWorkHandler;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700115 private HandlerThread mWorkThread;
Jason Monk30d80042015-05-08 16:54:18 -0400116
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700117 private WifiTrackerNetworkCallback mNetworkCallback;
118
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800119 /**
120 * Synchronization lock for managing concurrency between main and worker threads.
121 *
122 * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}.
123 */
124 private final Object mLock = new Object();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700125
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800126 /** The list of AccessPoints, aggregated visible ScanResults with metadata. */
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700127 @GuardedBy("mLock")
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700128 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
129
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800130 @GuardedBy("mLock")
131 private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700132
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800133 /**
134 * Tracks whether fresh scan results have been received since scanning start.
135 *
136 * <p>If this variable is false, we will not evict the scan result cache or invoke callbacks
137 * so that we do not update the UI with stale data / clear out existing UI elements prematurely.
138 */
139 private boolean mStaleScanResults = true;
140
141 // Does not need to be locked as it only updated on the worker thread, with the exception of
142 // during onStart, which occurs before the receiver is registered on the work handler.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700143 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800144 private boolean mRegistered;
Jason Monkd52356a2015-01-28 10:40:41 -0500145
146 private NetworkInfo mLastNetworkInfo;
147 private WifiInfo mLastInfo;
148
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800149 private final NetworkScoreManager mNetworkScoreManager;
Tony Mantler0edf09b2017-09-28 15:03:37 -0700150 private WifiNetworkScoreCache mScoreCache;
Sundeep Ghumane869d832017-01-25 16:23:43 -0800151 private boolean mNetworkScoringUiEnabled;
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700152 private long mMaxSpeedLabelScoreCacheAge;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800153
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800154
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700155
Jason Monkd52356a2015-01-28 10:40:41 -0500156 @VisibleForTesting
157 Scanner mScanner;
Sundeep Ghuman42058742017-07-21 18:42:10 -0700158
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700159 private static IntentFilter newIntentFilter() {
160 IntentFilter filter = new IntentFilter();
161 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
162 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
163 filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
164 filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
165 filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
166 filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
167 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
168 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
169
170 return filter;
171 }
172
Tony Mantler0edf09b2017-09-28 15:03:37 -0700173 /**
174 * Use the lifecycle constructor below whenever possible
175 */
176 @Deprecated
Jason Monk30d80042015-05-08 16:54:18 -0400177 public WifiTracker(Context context, WifiListener wifiListener,
178 boolean includeSaved, boolean includeScans) {
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800179 this(context, new WifiListenerExecutor(wifiListener),
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900180 context.getSystemService(WifiManager.class),
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800181 context.getSystemService(ConnectivityManager.class),
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700182 context.getSystemService(NetworkScoreManager.class),
Tony Mantler0edf09b2017-09-28 15:03:37 -0700183 newIntentFilter());
184 }
185
Sundeep Ghuman8d157e32018-01-24 20:32:35 -0800186 // TODO(Sghuman): Clean up includeSaved and includeScans from all constructors and linked
187 // calling apps once IC window is complete
Tony Mantler0edf09b2017-09-28 15:03:37 -0700188 public WifiTracker(Context context, WifiListener wifiListener,
189 @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800190 this(context, new WifiListenerExecutor(wifiListener),
Tony Mantler0edf09b2017-09-28 15:03:37 -0700191 context.getSystemService(WifiManager.class),
192 context.getSystemService(ConnectivityManager.class),
193 context.getSystemService(NetworkScoreManager.class),
194 newIntentFilter());
195 lifecycle.addObserver(this);
Jason Monkd52356a2015-01-28 10:40:41 -0500196 }
197
198 @VisibleForTesting
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800199 WifiTracker(Context context, WifiListenerExecutor wifiListenerExecutor,
Tony Mantler0edf09b2017-09-28 15:03:37 -0700200 WifiManager wifiManager, ConnectivityManager connectivityManager,
201 NetworkScoreManager networkScoreManager,
202 IntentFilter filter) {
Jason Monkd52356a2015-01-28 10:40:41 -0500203 mContext = context;
204 mWifiManager = wifiManager;
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800205 mListener = wifiListenerExecutor;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900206 mConnectivityManager = connectivityManager;
Jason Monkd52356a2015-01-28 10:40:41 -0500207
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800208 // check if verbose logging developer option has been turned on or off
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700209 sVerboseLogging = (mWifiManager.getVerboseLoggingLevel() > 0);
Jason Monkd52356a2015-01-28 10:40:41 -0500210
Eric Schwarzenbach5e0535e2017-08-16 15:47:33 -0700211 mFilter = filter;
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900212
213 mNetworkRequest = new NetworkRequest.Builder()
214 .clearCapabilities()
215 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
216 .build();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800217
218 mNetworkScoreManager = networkScoreManager;
219
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800220 // TODO(sghuman): Remove this and create less hacky solution for testing
Tony Mantler0edf09b2017-09-28 15:03:37 -0700221 final HandlerThread workThread = new HandlerThread(TAG
222 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
223 Process.THREAD_PRIORITY_BACKGROUND);
224 workThread.start();
225 setWorkThread(workThread);
226 }
227
228 /**
229 * Sanity warning: this wipes out mScoreCache, so use with extreme caution
230 * @param workThread substitute Handler thread, for testing purposes only
231 */
232 @VisibleForTesting
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800233 // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in
234 // during construction
Tony Mantler0edf09b2017-09-28 15:03:37 -0700235 void setWorkThread(HandlerThread workThread) {
236 mWorkThread = workThread;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800237 mWorkHandler = new Handler(workThread.getLooper());
Tony Mantler0edf09b2017-09-28 15:03:37 -0700238 mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800239 @Override
240 public void networkCacheUpdated(List<ScoredNetwork> networks) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800241 if (!mRegistered) return;
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700242
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800243 if (Log.isLoggable(TAG, Log.VERBOSE)) {
244 Log.v(TAG, "Score cache was updated with networks: " + networks);
245 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700246 updateNetworkScores();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800247 }
248 });
Jason Monkd52356a2015-01-28 10:40:41 -0500249 }
250
Tony Mantler0edf09b2017-09-28 15:03:37 -0700251 @Override
252 public void onDestroy() {
253 mWorkThread.quit();
254 }
255
Jason Monkd52356a2015-01-28 10:40:41 -0500256 /**
Jason Monkd52356a2015-01-28 10:40:41 -0500257 * Temporarily stop scanning for wifi networks.
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800258 *
259 * <p>Sets {@link #mStaleScanResults} to true.
Jason Monkd52356a2015-01-28 10:40:41 -0500260 */
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800261 private void pauseScanning() {
Jason Monkd52356a2015-01-28 10:40:41 -0500262 if (mScanner != null) {
263 mScanner.pause();
Jason Monk6572eae2015-02-05 10:34:20 -0500264 mScanner = null;
Jason Monkd52356a2015-01-28 10:40:41 -0500265 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800266 mStaleScanResults = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500267 }
268
269 /**
270 * Resume scanning for wifi networks after it has been paused.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800271 *
272 * <p>The score cache should be registered before this method is invoked.
Jason Monkd52356a2015-01-28 10:40:41 -0500273 */
274 public void resumeScanning() {
Jason Monk6572eae2015-02-05 10:34:20 -0500275 if (mScanner == null) {
276 mScanner = new Scanner();
277 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700278
Jason Monkd52356a2015-01-28 10:40:41 -0500279 if (mWifiManager.isWifiEnabled()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500280 mScanner.resume();
281 }
Jason Monkd52356a2015-01-28 10:40:41 -0500282 }
283
284 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800285 * Start tracking wifi networks and scores.
286 *
287 * <p>Registers listeners and starts scanning for wifi networks. If this is not called
Jason Monkd52356a2015-01-28 10:40:41 -0500288 * then forceUpdate() must be called to populate getAccessPoints().
289 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700290 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700291 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700292 public void onStart() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800293 // fetch current ScanResults instead of waiting for broadcast of fresh results
294 forceUpdate();
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800295
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800296 registerScoreCache();
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700297
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800298 mNetworkScoringUiEnabled =
299 Settings.Global.getInt(
300 mContext.getContentResolver(),
301 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800302
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800303 mMaxSpeedLabelScoreCacheAge =
304 Settings.Global.getLong(
305 mContext.getContentResolver(),
306 Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
307 DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS);
Sundeep Ghumanf3421742018-02-08 11:18:42 -0800308
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800309 resumeScanning();
310 if (!mRegistered) {
311 mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler);
312 // NetworkCallback objects cannot be reused. http://b/20701525 .
313 mNetworkCallback = new WifiTrackerNetworkCallback();
314 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
315 mRegistered = true;
Jason Monkd52356a2015-01-28 10:40:41 -0500316 }
317 }
318
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800319
320 /**
321 * Synchronously update the list of access points with the latest information.
322 *
323 * <p>Intended to only be invoked within {@link #onStart()}.
324 */
325 @MainThread
326 private void forceUpdate() {
327 mLastInfo = mWifiManager.getConnectionInfo();
328 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
329
330 fetchScansAndConfigsAndUpdateAccessPoints();
331 }
332
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800333 private void registerScoreCache() {
334 mNetworkScoreManager.registerNetworkScoreCache(
335 NetworkKey.TYPE_WIFI,
336 mScoreCache,
337 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
338 }
339
340 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
341 if (keys.isEmpty()) return;
342
Sundeep Ghuman39ad5d82017-07-11 19:03:51 -0700343 if (DBG()) {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800344 Log.d(TAG, "Requesting scores for Network Keys: " + keys);
345 }
346 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700347 synchronized (mLock) {
348 mRequestedScores.addAll(keys);
349 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800350 }
351
Jason Monkd52356a2015-01-28 10:40:41 -0500352 /**
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800353 * Stop tracking wifi networks and scores.
354 *
Tony Mantler0edf09b2017-09-28 15:03:37 -0700355 * <p>This should always be called when done with a WifiTracker (if onStart was called) to
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700356 * ensure proper cleanup and prevent any further callbacks from occurring.
357 *
Sundeep Ghumand1e44922017-08-07 11:21:38 -0700358 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700359 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
360 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
Jason Monkd52356a2015-01-28 10:40:41 -0500361 */
Tony Mantler0edf09b2017-09-28 15:03:37 -0700362 @Override
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700363 @MainThread
Tony Mantler0edf09b2017-09-28 15:03:37 -0700364 public void onStop() {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800365 if (mRegistered) {
366 mContext.unregisterReceiver(mReceiver);
367 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
368 mRegistered = false;
Jason Monkd52356a2015-01-28 10:40:41 -0500369 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800370 unregisterScoreCache();
371 pauseScanning(); // and set mStaleScanResults
372
373 mWorkHandler.removeCallbacksAndMessages(null /* remove all */);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800374 }
375
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700376 private void unregisterScoreCache() {
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800377 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
Sundeep Ghumanac7b4362017-02-08 17:19:27 -0800378
Sundeep Ghuman4bb84112017-06-26 16:05:40 -0700379 // We do not want to clear the existing scores in the cache, as this method is called during
380 // stop tracking on activity pause. Hence, on resumption we want the ability to show the
381 // last known, potentially stale, scores. However, by clearing requested scores, the scores
382 // will be requested again upon resumption of tracking, and if any changes have occurred
383 // the listeners (UI) will be updated accordingly.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700384 synchronized (mLock) {
385 mRequestedScores.clear();
386 }
Jason Monkd52356a2015-01-28 10:40:41 -0500387 }
388
389 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800390 * Gets the current list of access points.
391 *
392 * <p>This method is can be called on an abitrary thread by clients, but is normally called on
393 * the UI Thread by the rendering App.
Jason Monkd52356a2015-01-28 10:40:41 -0500394 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800395 @AnyThread
Jason Monkd52356a2015-01-28 10:40:41 -0500396 public List<AccessPoint> getAccessPoints() {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800397 synchronized (mLock) {
398 return new ArrayList<>(mInternalAccessPoints);
399 }
Jason Monkd52356a2015-01-28 10:40:41 -0500400 }
401
402 public WifiManager getManager() {
403 return mWifiManager;
404 }
405
406 public boolean isWifiEnabled() {
407 return mWifiManager.isWifiEnabled();
408 }
409
410 /**
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700411 * Returns the number of saved networks on the device, regardless of whether the WifiTracker
412 * is tracking saved networks.
Peter Qiuabea7262017-05-31 10:18:17 -0700413 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
414 * directly.
Jason Monkd52356a2015-01-28 10:40:41 -0500415 */
Amin Shaikha80ae0c2017-03-23 16:18:30 -0700416 public int getNumSavedNetworks() {
Peter Qiuabea7262017-05-31 10:18:17 -0700417 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
Jason Monkd52356a2015-01-28 10:40:41 -0500418 }
419
420 public boolean isConnected() {
421 return mConnected.get();
422 }
423
424 public void dump(PrintWriter pw) {
425 pw.println(" - wifi tracker ------");
Jason Monk19040812015-06-01 15:00:57 -0400426 for (AccessPoint accessPoint : getAccessPoints()) {
Jason Monkd52356a2015-01-28 10:40:41 -0500427 pw.println(" " + accessPoint);
428 }
429 }
430
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800431 private ArrayMap<String, List<ScanResult>> updateScanResultCache(
432 final List<ScanResult> newResults) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800433 // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for
434 // memory efficiency
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700435 for (ScanResult newResult : newResults) {
Mitchell Wills9df79042015-09-01 12:21:59 -0700436 if (newResult.SSID == null || newResult.SSID.isEmpty()) {
437 continue;
438 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700439 mScanResultCache.put(newResult.BSSID, newResult);
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700440 }
441
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800442 // Don't evict old results if no new scan results
443 if (!mStaleScanResults) {
444 evictOldScans();
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700445 }
446
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800447 ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>();
448 for (ScanResult result : mScanResultCache.values()) {
449 // Ignore hidden and ad-hoc networks.
450 if (result.SSID == null || result.SSID.length() == 0 ||
451 result.capabilities.contains("[IBSS]")) {
452 continue;
453 }
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800454
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800455 String apKey = AccessPoint.getKey(result);
456 List<ScanResult> resultList;
457 if (scanResultsByApKey.containsKey(apKey)) {
458 resultList = scanResultsByApKey.get(apKey);
459 } else {
460 resultList = new ArrayList<>();
461 scanResultsByApKey.put(apKey, resultList);
462 }
463
464 resultList.add(result);
465 }
466
467 return scanResultsByApKey;
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700468 }
469
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800470 /**
471 * Remove old scan results from the cache.
472 *
473 * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when
474 * {@link #mStaleScanResults} is false.
475 */
476 private void evictOldScans() {
477 long nowMs = SystemClock.elapsedRealtime();
478 for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
479 ScanResult result = iter.next();
480 // result timestamp is in microseconds
481 if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) {
482 iter.remove();
483 }
484 }
485 }
486
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700487 private WifiConfiguration getWifiConfigurationForNetworkId(
488 int networkId, final List<WifiConfiguration> configs) {
Mitchell Wills5a42db22015-08-03 09:46:08 -0700489 if (configs != null) {
490 for (WifiConfiguration config : configs) {
491 if (mLastInfo != null && networkId == config.networkId &&
492 !(config.selfAdded && config.numAssociation == 0)) {
493 return config;
494 }
495 }
496 }
497 return null;
498 }
499
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700500 /**
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800501 * Retrieves latest scan results and wifi configs, then calls
502 * {@link #updateAccessPoints(List, List)}.
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700503 */
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800504 private void fetchScansAndConfigsAndUpdateAccessPoints() {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700505 final List<ScanResult> newScanResults = mWifiManager.getScanResults();
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800506 if (isVerboseLoggingEnabled()) {
Sundeep Ghumana28050a2017-07-12 22:09:25 -0700507 Log.i(TAG, "Fetched scan results: " + newScanResults);
508 }
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700509
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800510 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
511 updateAccessPoints(newScanResults, configs);
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700512 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700513
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800514 /** Update the internal list of access points. */
515 private void updateAccessPoints(final List<ScanResult> newScanResults,
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700516 List<WifiConfiguration> configs) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800517
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800518 // Map configs and scan results necessary to make AccessPoints
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800519 final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
Jason Monkd52356a2015-01-28 10:40:41 -0500520 if (configs != null) {
Jason Monkd52356a2015-01-28 10:40:41 -0500521 for (WifiConfiguration config : configs) {
Sundeep Ghuman0d492e82018-01-26 12:45:02 -0800522 configsByKey.put(AccessPoint.getKey(config), config);
Jason Monkd52356a2015-01-28 10:40:41 -0500523 }
524 }
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800525 ArrayMap<String, List<ScanResult>> scanResultsByApKey =
526 updateScanResultCache(newScanResults);
527
528 WifiConfiguration connectionConfig = null;
529 if (mLastInfo != null) {
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800530 // TODO(sghuman): Refactor to match config network id when updating configs below, and
531 // then update network info and wifi info only on match
Sundeep Ghumancf114b62018-01-29 20:07:16 -0800532 connectionConfig = getWifiConfigurationForNetworkId(
533 mLastInfo.getNetworkId(), configs);
534 }
535
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800536 // Rather than dropping and reacquiring the lock multiple times in this method, we lock
537 // once for efficiency of lock acquisition time and readability
538 synchronized (mLock) {
539 // Swap the current access points into a cached list for maintaining AP listeners
540 List<AccessPoint> cachedAccessPoints;
541 cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
Jason Monkd52356a2015-01-28 10:40:41 -0500542
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800543 ArrayList<AccessPoint> accessPoints = new ArrayList<>();
Jason Monkd52356a2015-01-28 10:40:41 -0500544
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800545 final List<NetworkKey> scoresToRequest = new ArrayList<>();
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800546
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800547 for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) {
548 for (ScanResult result : entry.getValue()) {
549 NetworkKey key = NetworkKey.createFromScanResult(result);
550 if (key != null && !mRequestedScores.contains(key)) {
551 scoresToRequest.add(key);
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800552 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700553 }
Vinit Deshpandefcd46122015-06-11 18:22:23 -0700554
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800555 AccessPoint accessPoint =
556 getCachedOrCreate(entry.getValue(), cachedAccessPoints);
557 if (mLastInfo != null && mLastNetworkInfo != null) {
558 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
559 }
560
561 // Update the matching config if there is one, to populate saved network info
562 accessPoint.update(configsByKey.get(entry.getKey()));
563
564 accessPoints.add(accessPoint);
565 }
566
567 requestScoresForNetworkKeys(scoresToRequest);
568 for (AccessPoint ap : accessPoints) {
569 ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge);
570 }
571
572 // Pre-sort accessPoints to speed preference insertion
573 Collections.sort(accessPoints);
574
575 // Log accesspoints that are being removed
576 if (DBG()) {
577 Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
578 for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
579 if (prevAccessPoint.getSsid() == null)
580 continue;
581 String prevSsid = prevAccessPoint.getSsidStr();
582 boolean found = false;
583 for (AccessPoint newAccessPoint : accessPoints) {
584 if (newAccessPoint.getSsidStr() != null && newAccessPoint.getSsidStr()
585 .equals(prevSsid)) {
586 found = true;
587 break;
588 }
589 }
590 if (!found)
591 Log.d(TAG, "Did not find " + prevSsid + " in this scan");
592 }
593 Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
594 }
595
596 mInternalAccessPoints.clear();
597 mInternalAccessPoints.addAll(accessPoints);
598 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700599
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800600 conditionallyNotifyListeners();
Jason Monkd52356a2015-01-28 10:40:41 -0500601 }
602
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700603 @VisibleForTesting
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800604 AccessPoint getCachedOrCreate(
605 List<ScanResult> scanResults,
606 List<AccessPoint> cache) {
Jason Monk30d80042015-05-08 16:54:18 -0400607 final int N = cache.size();
Jason Monkd52356a2015-01-28 10:40:41 -0500608 for (int i = 0; i < N; i++) {
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800609 if (cache.get(i).getKey().equals(AccessPoint.getKey(scanResults.get(0)))) {
Jason Monk30d80042015-05-08 16:54:18 -0400610 AccessPoint ret = cache.remove(i);
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800611 ret.setScanResults(scanResults);
Jason Monkd52356a2015-01-28 10:40:41 -0500612 return ret;
613 }
614 }
Sundeep Ghuman04f7f342018-01-23 19:18:31 -0800615 final AccessPoint accessPoint = new AccessPoint(mContext, scanResults);
Ajay Nadathur7d176bc2016-10-24 16:55:24 -0700616 return accessPoint;
Jason Monkd52356a2015-01-28 10:40:41 -0500617 }
618
Jason Monkd52356a2015-01-28 10:40:41 -0500619 private void updateNetworkInfo(NetworkInfo networkInfo) {
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800620
621 /* Sticky broadcasts can call this when wifi is disabled */
Jason Monke04ae8a2015-06-03 14:06:34 -0400622 if (!mWifiManager.isWifiEnabled()) {
Sundeep Ghumane8013092017-06-21 22:35:30 -0700623 clearAccessPointsAndConditionallyUpdate();
Jason Monke04ae8a2015-06-03 14:06:34 -0400624 return;
625 }
Jason Monkd52356a2015-01-28 10:40:41 -0500626
Jason Monkd52356a2015-01-28 10:40:41 -0500627 if (networkInfo != null) {
628 mLastNetworkInfo = networkInfo;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700629 if (DBG()) {
630 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo);
631 }
Sundeep Ghumanc5bbecb2018-01-29 18:56:36 -0800632
633 if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) {
634 mListener.onConnectedChanged();
635 }
Jason Monkd52356a2015-01-28 10:40:41 -0500636 }
637
Mitchell Wills5a42db22015-08-03 09:46:08 -0700638 WifiConfiguration connectionConfig = null;
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700639
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900640 mLastInfo = mWifiManager.getConnectionInfo();
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700641 if (DBG()) {
642 Log.d(TAG, "mLastInfo set as: " + mLastInfo);
643 }
Mitchell Wills5a42db22015-08-03 09:46:08 -0700644 if (mLastInfo != null) {
Sundeep Ghuman266b4092017-06-16 15:12:56 -0700645 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(),
646 mWifiManager.getConfiguredNetworks());
Mitchell Wills5a42db22015-08-03 09:46:08 -0700647 }
648
Sundeep Ghuman8c792882017-04-04 17:23:29 -0700649 boolean updated = false;
650 boolean reorder = false; // Only reorder if connected AP was changed
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700651
652 synchronized (mLock) {
653 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
654 AccessPoint ap = mInternalAccessPoints.get(i);
655 boolean previouslyConnected = ap.isActive();
656 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
657 updated = true;
658 if (previouslyConnected != ap.isActive()) reorder = true;
659 }
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700660 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700661 reorder = true;
662 updated = true;
663 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800664 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700665
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800666 if (reorder) {
667 Collections.sort(mInternalAccessPoints);
668 }
669 if (updated) {
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800670 conditionallyNotifyListeners();
Sundeep Ghuman91f4ccb2018-01-26 18:58:56 -0800671 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800672 }
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800673 }
674
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800675 /**
676 * Clears the access point list and conditionally invokes
677 * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already
678 * empty).
679 */
Sundeep Ghumane8013092017-06-21 22:35:30 -0700680 private void clearAccessPointsAndConditionallyUpdate() {
681 synchronized (mLock) {
682 if (!mInternalAccessPoints.isEmpty()) {
683 mInternalAccessPoints.clear();
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -0800684 conditionallyNotifyListeners();
Sundeep Ghumane8013092017-06-21 22:35:30 -0700685 }
686 }
687 }
688
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800689 /**
Stephen Chen21f68682017-04-04 13:23:31 -0700690 * Update all the internal access points rankingScores, badge and metering.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800691 *
692 * <p>Will trigger a resort and notify listeners of changes if applicable.
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700693 *
694 * <p>Synchronized on {@link #mLock}.
Sundeep Ghuman5519b7b2016-12-14 17:53:31 -0800695 */
696 private void updateNetworkScores() {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700697 synchronized (mLock) {
698 boolean updated = false;
699 for (int i = 0; i < mInternalAccessPoints.size(); i++) {
Sundeep Ghuman9bb85d32017-08-28 17:04:16 -0700700 if (mInternalAccessPoints.get(i).update(
701 mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) {
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700702 updated = true;
703 }
Jason Monkd52356a2015-01-28 10:40:41 -0500704 }
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700705 if (updated) {
706 Collections.sort(mInternalAccessPoints);
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800707 conditionallyNotifyListeners();
Sundeep Ghumance6bab82017-04-19 16:21:46 -0700708 }
Jason Monkd52356a2015-01-28 10:40:41 -0500709 }
710 }
711
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800712 /**
713 * Receiver for handling broadcasts.
714 *
715 * This receiver is registered on the WorkHandler.
716 */
Jason Monkd52356a2015-01-28 10:40:41 -0500717 @VisibleForTesting
718 final BroadcastReceiver mReceiver = new BroadcastReceiver() {
719 @Override
720 public void onReceive(Context context, Intent intent) {
721 String action = intent.getAction();
Sundeep Ghumand4a79ac2017-06-07 18:11:39 -0700722
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800723 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
724 updateWifiState(
725 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
726 WifiManager.WIFI_STATE_UNKNOWN));
727 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
728 mStaleScanResults = false;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800729
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800730 fetchScansAndConfigsAndUpdateAccessPoints();
731 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)
732 || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
733 fetchScansAndConfigsAndUpdateAccessPoints();
734 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
735 // TODO(sghuman): Refactor these methods so they cannot result in duplicate
736 // onAccessPointsChanged updates being called from this intent.
737 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
738 updateNetworkInfo(info);
739 fetchScansAndConfigsAndUpdateAccessPoints();
740 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
741 NetworkInfo info =
742 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
743 updateNetworkInfo(info);
Jason Monkd52356a2015-01-28 10:40:41 -0500744 }
745 }
746 };
747
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800748 /**
749 * Handles updates to WifiState.
750 *
751 * <p>If Wifi is not enabled in the enabled state, {@link #mStaleScanResults} will be set to
752 * true.
753 */
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800754 private void updateWifiState(int state) {
755 if (state == WifiManager.WIFI_STATE_ENABLED) {
756 if (mScanner != null) {
757 // We only need to resume if mScanner isn't null because
758 // that means we want to be scanning.
759 mScanner.resume();
760 }
761 } else {
762 clearAccessPointsAndConditionallyUpdate();
763 mLastInfo = null;
764 mLastNetworkInfo = null;
765 if (mScanner != null) {
766 mScanner.pause();
767 }
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800768 mStaleScanResults = true;
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800769 }
770 mListener.onWifiStateChanged(state);
771 }
772
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900773 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
774 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
775 if (network.equals(mWifiManager.getCurrentNetwork())) {
Sundeep Ghumanbcb53732018-01-31 12:42:16 -0800776 // TODO(sghuman): Investigate whether this comment still holds true and if it makes
777 // more sense fetch the latest network info here:
778
Lorenzo Colittiab313f82015-11-26 16:55:15 +0900779 // We don't send a NetworkInfo object along with this message, because even if we
780 // fetch one from ConnectivityManager, it might be older than the most recent
781 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
Sundeep Ghuman5886a942018-02-06 19:58:09 -0800782 mWorkHandler.post(() -> updateNetworkInfo(null));
Jason Monk30d80042015-05-08 16:54:18 -0400783 }
784 }
785 }
786
Jason Monkd52356a2015-01-28 10:40:41 -0500787 @VisibleForTesting
788 class Scanner extends Handler {
Jason Monk2b51cc32015-05-13 11:07:53 -0400789 static final int MSG_SCAN = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500790
Jason Monkd52356a2015-01-28 10:40:41 -0500791 private int mRetry = 0;
792
793 void resume() {
Jason Monk6572eae2015-02-05 10:34:20 -0500794 if (!hasMessages(MSG_SCAN)) {
795 sendEmptyMessage(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500796 }
797 }
798
Jason Monkd52356a2015-01-28 10:40:41 -0500799 void pause() {
800 mRetry = 0;
Jason Monk6572eae2015-02-05 10:34:20 -0500801 removeMessages(MSG_SCAN);
802 }
803
804 @VisibleForTesting
805 boolean isScanning() {
806 return hasMessages(MSG_SCAN);
Jason Monkd52356a2015-01-28 10:40:41 -0500807 }
808
809 @Override
810 public void handleMessage(Message message) {
Jason Monk6572eae2015-02-05 10:34:20 -0500811 if (message.what != MSG_SCAN) return;
Jason Monkd52356a2015-01-28 10:40:41 -0500812 if (mWifiManager.startScan()) {
813 mRetry = 0;
814 } else if (++mRetry >= 3) {
815 mRetry = 0;
816 if (mContext != null) {
817 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
818 }
819 return;
820 }
Sundeep Ghumandb9b94c2017-09-06 11:46:21 -0700821 sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS);
Jason Monkd52356a2015-01-28 10:40:41 -0500822 }
823 }
824
825 /** A restricted multimap for use in constructAccessPoints */
826 private static class Multimap<K,V> {
827 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
828 /** retrieve a non-null list of values with key K */
829 List<V> getAll(K key) {
830 List<V> values = store.get(key);
831 return values != null ? values : Collections.<V>emptyList();
832 }
833
834 void put(K key, V val) {
835 List<V> curVals = store.get(key);
836 if (curVals == null) {
837 curVals = new ArrayList<V>(3);
838 store.put(key, curVals);
839 }
840 curVals.add(val);
841 }
842 }
843
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800844 /**
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800845 * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800846 *
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800847 * <p>Also logs all callbacks invocations when verbose logging is enabled.
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800848 */
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800849 @VisibleForTesting
850 public static class WifiListenerExecutor implements WifiListener {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800851
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800852 private final WifiListener mDelegatee;
853
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800854 public WifiListenerExecutor(WifiListener listener) {
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800855 mDelegatee = listener;
856 }
857
858 @Override
859 public void onWifiStateChanged(int state) {
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800860 if (isVerboseLoggingEnabled()) {
861 Log.i(TAG,
862 String.format("Invoking onWifiStateChanged callback with state %d", state));
863 }
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800864 ThreadUtils.postOnMainThread(() -> mDelegatee.onWifiStateChanged(state));
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800865 }
866
867 @Override
868 public void onConnectedChanged() {
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800869 if (isVerboseLoggingEnabled()) {
870 Log.i(TAG, "Invoking onConnectedChanged callback");
871 }
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800872 ThreadUtils.postOnMainThread(() -> mDelegatee.onConnectedChanged());
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800873 }
874
875 @Override
876 public void onAccessPointsChanged() {
Sundeep Ghumanc0cf8482018-01-26 18:23:34 -0800877 if (isVerboseLoggingEnabled()) {
878 Log.i(TAG, "Invoking onAccessPointsChanged callback");
879 }
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800880 ThreadUtils.postOnMainThread(() -> mDelegatee.onAccessPointsChanged());
Sundeep Ghuman86cc9b72018-01-24 20:08:39 -0800881 }
882 }
883
Sundeep Ghumandc67f4d2018-01-31 16:03:33 -0800884 /**
885 * WifiListener interface that defines callbacks indicating state changes in WifiTracker.
886 *
887 * <p>All callbacks are invoked on the MainThread.
888 */
Jason Monkd52356a2015-01-28 10:40:41 -0500889 public interface WifiListener {
890 /**
891 * Called when the state of Wifi has changed, the state will be one of
892 * the following.
893 *
894 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
895 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
896 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
897 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
898 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
899 * <p>
900 *
901 * @param state The new state of wifi.
902 */
903 void onWifiStateChanged(int state);
904
905 /**
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800906 * Called when the connection state of wifi has changed and
907 * {@link WifiTracker#isConnected()} should be called to get the updated state.
Jason Monkd52356a2015-01-28 10:40:41 -0500908 */
909 void onConnectedChanged();
910
911 /**
912 * Called to indicate the list of AccessPoints has been updated and
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800913 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
Jason Monkd52356a2015-01-28 10:40:41 -0500914 */
915 void onAccessPointsChanged();
916 }
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700917
918 /**
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -0800919 * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults}
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800920 * is false.
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700921 */
Sundeep Ghumanbb399912018-01-29 18:31:15 -0800922 private void conditionallyNotifyListeners() {
923 if (mStaleScanResults) {
924 return;
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700925 }
926
Sundeep Ghuman062bf5a2018-02-21 14:17:57 -0800927 mListener.onAccessPointsChanged();
Ajay Nadathurd7b689a2016-08-31 15:07:56 -0700928 }
Jason Monkd52356a2015-01-28 10:40:41 -0500929}