1/*
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
18import android.annotation.MainThread;
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.database.ContentObserver;
24import android.net.ConnectivityManager;
25import android.net.Network;
26import android.net.NetworkCapabilities;
27import android.net.NetworkInfo;
28import android.net.NetworkInfo.DetailedState;
29import android.net.NetworkKey;
30import android.net.NetworkRequest;
31import android.net.NetworkScoreManager;
32import android.net.ScoredNetwork;
33import android.net.wifi.ScanResult;
34import android.net.wifi.WifiConfiguration;
35import android.net.wifi.WifiInfo;
36import android.net.wifi.WifiManager;
37import android.net.wifi.WifiNetworkScoreCache;
38import android.net.wifi.WifiNetworkScoreCache.CacheListener;
39import android.os.ConditionVariable;
40import android.os.Handler;
41import android.os.Looper;
42import android.os.Message;
43import android.provider.Settings;
44import android.support.annotation.GuardedBy;
45import android.util.ArraySet;
46import android.util.Log;
47import android.util.SparseArray;
48import android.util.SparseIntArray;
49import android.widget.Toast;
50
51import com.android.internal.annotations.VisibleForTesting;
52import com.android.settingslib.R;
53
54import java.io.PrintWriter;
55import java.util.ArrayList;
56import java.util.Collection;
57import java.util.Collections;
58import java.util.HashMap;
59import java.util.Iterator;
60import java.util.List;
61import java.util.Map;
62import java.util.Set;
63import java.util.concurrent.atomic.AtomicBoolean;
64
65/**
66 * Tracks saved or available wifi networks and their state.
67 */
68public class WifiTracker {
69    // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
70
71    private static final String TAG = "WifiTracker";
72    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
73
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;
81    private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
82
83    private final Context mContext;
84    private final WifiManager mWifiManager;
85    private final IntentFilter mFilter;
86    private final ConnectivityManager mConnectivityManager;
87    private final NetworkRequest mNetworkRequest;
88    private final AtomicBoolean mConnected = new AtomicBoolean(false);
89    private final WifiListener mListener;
90    private final boolean mIncludeSaved;
91    private final boolean mIncludeScans;
92    private final boolean mIncludePasspoints;
93    @VisibleForTesting final MainHandler mMainHandler;
94    private final WorkHandler mWorkHandler;
95
96    private WifiTrackerNetworkCallback mNetworkCallback;
97
98    @GuardedBy("mLock")
99    private boolean mRegistered;
100
101    /**
102     * The externally visible access point list.
103     *
104     * Updated using main handler. Clone of this collection is returned from
105     * {@link #getAccessPoints()}
106     */
107    private final List<AccessPoint> mAccessPoints = new ArrayList<>();
108
109    /**
110     * The internal list of access points, synchronized on itself.
111     *
112     * Never exposed outside this class.
113     */
114    @GuardedBy("mLock")
115    private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>();
116
117    /**
118    * Synchronization lock for managing concurrency between main and worker threads.
119    *
120    * <p>This lock should be held for all background work.
121    * TODO(b/37674366): Remove the worker thread so synchronization is no longer necessary.
122    */
123    private final Object mLock = new Object();
124
125    //visible to both worker and main thread.
126    @GuardedBy("mLock")
127    private final AccessPointListenerAdapter mAccessPointListenerAdapter
128            = new AccessPointListenerAdapter();
129
130    private final HashMap<String, Integer> mSeenBssids = new HashMap<>();
131    private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
132    private Integer mScanId = 0;
133
134    private NetworkInfo mLastNetworkInfo;
135    private WifiInfo mLastInfo;
136
137    private final NetworkScoreManager mNetworkScoreManager;
138    private final WifiNetworkScoreCache mScoreCache;
139
140    @GuardedBy("mLock")
141    private final Set<NetworkKey> mRequestedScores = new ArraySet<>();
142
143    @VisibleForTesting
144    Scanner mScanner;
145    private boolean mStaleScanResults = false;
146
147    public WifiTracker(Context context, WifiListener wifiListener,
148            boolean includeSaved, boolean includeScans) {
149        this(context, wifiListener, null, includeSaved, includeScans);
150    }
151
152    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
153            boolean includeSaved, boolean includeScans) {
154        this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
155    }
156
157    public WifiTracker(Context context, WifiListener wifiListener,
158            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
159        this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
160    }
161
162    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
163            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
164        this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
165                context.getSystemService(WifiManager.class),
166                context.getSystemService(ConnectivityManager.class),
167                context.getSystemService(NetworkScoreManager.class), Looper.myLooper()
168        );
169    }
170
171    @VisibleForTesting
172    WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
173            boolean includeSaved, boolean includeScans, boolean includePasspoints,
174            WifiManager wifiManager, ConnectivityManager connectivityManager,
175            NetworkScoreManager networkScoreManager, Looper currentLooper) {
176        if (!includeSaved && !includeScans) {
177            throw new IllegalArgumentException("Must include either saved or scans");
178        }
179        mContext = context;
180        if (currentLooper == null) {
181            // When we aren't on a looper thread, default to the main.
182            currentLooper = Looper.getMainLooper();
183        }
184        mMainHandler = new MainHandler(currentLooper);
185        mWorkHandler = new WorkHandler(
186                workerLooper != null ? workerLooper : currentLooper);
187        mWifiManager = wifiManager;
188        mIncludeSaved = includeSaved;
189        mIncludeScans = includeScans;
190        mIncludePasspoints = includePasspoints;
191        mListener = wifiListener;
192        mConnectivityManager = connectivityManager;
193
194        // check if verbose logging has been turned on or off
195        sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
196
197        mFilter = new IntentFilter();
198        mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
199        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
200        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
201        mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
202        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
203        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
204        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
205        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
206
207        mNetworkRequest = new NetworkRequest.Builder()
208                .clearCapabilities()
209                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
210                .build();
211
212        mNetworkScoreManager = networkScoreManager;
213
214        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
215            @Override
216            public void networkCacheUpdated(List<ScoredNetwork> networks) {
217                synchronized (mLock) {
218                    if (!mRegistered) return;
219                }
220
221                if (Log.isLoggable(TAG, Log.VERBOSE)) {
222                    Log.v(TAG, "Score cache was updated with networks: " + networks);
223                }
224                updateNetworkScores();
225            }
226        });
227    }
228
229    /**
230     * Synchronously update the list of access points with the latest information.
231     */
232    @MainThread
233    public void forceUpdate() {
234        synchronized (mLock) {
235            mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
236            mLastInfo = mWifiManager.getConnectionInfo();
237            mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
238            updateAccessPointsLocked();
239
240            if (DBG) {
241                Log.d(TAG, "force update - internal access point list:\n" + mInternalAccessPoints);
242            }
243
244            // Synchronously copy access points
245            mMainHandler.removeMessages(MainHandler.MSG_ACCESS_POINT_CHANGED);
246            mMainHandler.handleMessage(
247                    Message.obtain(mMainHandler, MainHandler.MSG_ACCESS_POINT_CHANGED));
248            if (DBG) {
249                Log.d(TAG, "force update - external access point list:\n" + mAccessPoints);
250            }
251        }
252    }
253
254    /**
255     * Force a scan for wifi networks to happen now.
256     */
257    public void forceScan() {
258        if (mWifiManager.isWifiEnabled() && mScanner != null) {
259            mScanner.forceScan();
260        }
261    }
262
263    /**
264     * Temporarily stop scanning for wifi networks.
265     */
266    public void pauseScanning() {
267        if (mScanner != null) {
268            mScanner.pause();
269            mScanner = null;
270        }
271    }
272
273    /**
274     * Resume scanning for wifi networks after it has been paused.
275     *
276     * <p>The score cache should be registered before this method is invoked.
277     */
278    public void resumeScanning() {
279        if (mScanner == null) {
280            mScanner = new Scanner();
281        }
282
283        mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
284        if (mWifiManager.isWifiEnabled()) {
285            mScanner.resume();
286        }
287    }
288
289    /**
290     * Start tracking wifi networks and scores.
291     *
292     * <p>Registers listeners and starts scanning for wifi networks. If this is not called
293     * then forceUpdate() must be called to populate getAccessPoints().
294     */
295    @MainThread
296    public void startTracking() {
297        synchronized (mLock) {
298            registerScoreCache();
299
300            resumeScanning();
301            if (!mRegistered) {
302                mContext.registerReceiver(mReceiver, mFilter);
303                // NetworkCallback objects cannot be reused. http://b/20701525 .
304                mNetworkCallback = new WifiTrackerNetworkCallback();
305                mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
306                mRegistered = true;
307            }
308        }
309    }
310
311    private void registerScoreCache() {
312        mNetworkScoreManager.registerNetworkScoreCache(
313                NetworkKey.TYPE_WIFI,
314                mScoreCache,
315                NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
316    }
317
318    private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
319        if (keys.isEmpty()) return;
320
321        if (DBG) {
322            Log.d(TAG, "Requesting scores for Network Keys: " + keys);
323        }
324        mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()]));
325        synchronized (mLock) {
326            mRequestedScores.addAll(keys);
327        }
328    }
329
330    /**
331     * Stop tracking wifi networks and scores.
332     *
333     * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
334     * ensure proper cleanup and prevent any further callbacks from occurring.
335     *
336     * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
337     * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
338     * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
339     */
340    @MainThread
341    public void stopTracking() {
342        synchronized (mLock) {
343            if (mRegistered) {
344                mContext.unregisterReceiver(mReceiver);
345                mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
346                mRegistered = false;
347            }
348            unregisterAndClearScoreCache();
349            pauseScanning();
350
351            mWorkHandler.removePendingMessages();
352            mMainHandler.removePendingMessages();
353        }
354        mStaleScanResults = true;
355    }
356
357    private void unregisterAndClearScoreCache() {
358        mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
359        mScoreCache.clearScores();
360
361        // Synchronize on mLock to avoid concurrent modification during updateAccessPointsLocked
362        synchronized (mLock) {
363            mRequestedScores.clear();
364        }
365    }
366
367    /**
368     * Gets the current list of access points. Should be called from main thread, otherwise
369     * expect inconsistencies
370     */
371    @MainThread
372    public List<AccessPoint> getAccessPoints() {
373        return new ArrayList<>(mAccessPoints);
374    }
375
376    public WifiManager getManager() {
377        return mWifiManager;
378    }
379
380    public boolean isWifiEnabled() {
381        return mWifiManager.isWifiEnabled();
382    }
383
384    /**
385     * Returns the number of saved networks on the device, regardless of whether the WifiTracker
386     * is tracking saved networks.
387     * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils
388     * directly.
389     */
390    public int getNumSavedNetworks() {
391        return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size();
392    }
393
394    public boolean isConnected() {
395        return mConnected.get();
396    }
397
398    public void dump(PrintWriter pw) {
399        pw.println("  - wifi tracker ------");
400        for (AccessPoint accessPoint : getAccessPoints()) {
401            pw.println("  " + accessPoint);
402        }
403    }
404
405    private void handleResume() {
406        mScanResultCache.clear();
407        mSeenBssids.clear();
408        mScanId = 0;
409    }
410
411    private Collection<ScanResult> fetchScanResults() {
412        mScanId++;
413        final List<ScanResult> newResults = mWifiManager.getScanResults();
414        for (ScanResult newResult : newResults) {
415            if (newResult.SSID == null || newResult.SSID.isEmpty()) {
416                continue;
417            }
418            mScanResultCache.put(newResult.BSSID, newResult);
419            mSeenBssids.put(newResult.BSSID, mScanId);
420        }
421
422        if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
423            if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
424            Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
425            for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
426                    it.hasNext(); /* nothing */) {
427                Map.Entry<String, Integer> e = it.next();
428                if (e.getValue() < threshold) {
429                    ScanResult result = mScanResultCache.get(e.getKey());
430                    if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
431                    mScanResultCache.remove(e.getKey());
432                    it.remove();
433                }
434            }
435            if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
436        }
437
438        return mScanResultCache.values();
439    }
440
441    private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) {
442        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
443        if (configs != null) {
444            for (WifiConfiguration config : configs) {
445                if (mLastInfo != null && networkId == config.networkId &&
446                        !(config.selfAdded && config.numAssociation == 0)) {
447                    return config;
448                }
449            }
450        }
451        return null;
452    }
453
454    /** Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first. */
455    private void updateAccessPointsLocked() {
456        synchronized (mLock) {
457            updateAccessPoints();
458        }
459    }
460
461    /**
462     * Update the internal list of access points.
463     *
464     * <p>Should never be called directly, use {@link #updateAccessPointsLocked()} instead.
465     */
466    @GuardedBy("mLock")
467    private void updateAccessPoints() {
468        // Swap the current access points into a cached list.
469        List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints);
470        ArrayList<AccessPoint> accessPoints = new ArrayList<>();
471
472        // Clear out the configs so we don't think something is saved when it isn't.
473        for (AccessPoint accessPoint : cachedAccessPoints) {
474            accessPoint.clearConfig();
475        }
476
477    /* Lookup table to more quickly update AccessPoints by only considering objects with the
478     * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
479        Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
480        WifiConfiguration connectionConfig = null;
481        if (mLastInfo != null) {
482            connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
483        }
484
485        final Collection<ScanResult> results = fetchScanResults();
486
487        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
488        if (configs != null) {
489            for (WifiConfiguration config : configs) {
490                if (config.selfAdded && config.numAssociation == 0) {
491                    continue;
492                }
493                AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
494                if (mLastInfo != null && mLastNetworkInfo != null) {
495                    accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
496                }
497                if (mIncludeSaved) {
498                    // If saved network not present in scan result then set its Rssi to
499                    // UNREACHABLE_RSSI
500                    boolean apFound = false;
501                    for (ScanResult result : results) {
502                        if (result.SSID.equals(accessPoint.getSsidStr())) {
503                            apFound = true;
504                            break;
505                        }
506                    }
507                    if (!apFound) {
508                        accessPoint.setUnreachable();
509                    }
510                    accessPoints.add(accessPoint);
511                    apMap.put(accessPoint.getSsidStr(), accessPoint);
512                } else {
513                    // If we aren't using saved networks, drop them into the cache so that
514                    // we have access to their saved info.
515                    cachedAccessPoints.add(accessPoint);
516                }
517            }
518        }
519
520        final List<NetworkKey> scoresToRequest = new ArrayList<>();
521        if (results != null) {
522            for (ScanResult result : results) {
523                // Ignore hidden and ad-hoc networks.
524                if (result.SSID == null || result.SSID.length() == 0 ||
525                        result.capabilities.contains("[IBSS]")) {
526                    continue;
527                }
528
529                NetworkKey key = NetworkKey.createFromScanResult(result);
530                if (key != null && !mRequestedScores.contains(key)) {
531                    scoresToRequest.add(key);
532                }
533
534                boolean found = false;
535                for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
536                    if (accessPoint.update(result)) {
537                        found = true;
538                        break;
539                    }
540                }
541                if (!found && mIncludeScans) {
542                    AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
543                    if (mLastInfo != null && mLastNetworkInfo != null) {
544                        accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
545                    }
546
547                    if (result.isPasspointNetwork()) {
548                        // Retrieve a WifiConfiguration for a Passpoint provider that matches
549                        // the given ScanResult.  This is used for showing that a given AP
550                        // (ScanResult) is available via a Passpoint provider (provider friendly
551                        // name).
552                        try {
553                            WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
554                            if (config != null) {
555                                accessPoint.update(config);
556                            }
557                        } catch (UnsupportedOperationException e) {
558                            // Passpoint not supported on the device.
559                        }
560                    }
561
562                    accessPoints.add(accessPoint);
563                    apMap.put(accessPoint.getSsidStr(), accessPoint);
564                }
565            }
566        }
567
568        requestScoresForNetworkKeys(scoresToRequest);
569        for (AccessPoint ap : accessPoints) {
570            ap.update(mScoreCache, false /* mNetworkScoringUiEnabled */);
571        }
572
573        // Pre-sort accessPoints to speed preference insertion
574        Collections.sort(accessPoints);
575
576        // Log accesspoints that were deleted
577        if (DBG) {
578            Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
579            for (AccessPoint prevAccessPoint : mInternalAccessPoints) {
580                if (prevAccessPoint.getSsid() == null)
581                    continue;
582                String prevSsid = prevAccessPoint.getSsidStr();
583                boolean found = false;
584                for (AccessPoint newAccessPoint : accessPoints) {
585                    if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid()
586                            .equals(prevSsid)) {
587                        found = true;
588                        break;
589                    }
590                }
591                if (!found)
592                    Log.d(TAG, "Did not find " + prevSsid + " in this scan");
593            }
594            Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
595        }
596
597        mInternalAccessPoints.clear();
598        mInternalAccessPoints.addAll(accessPoints);
599
600        mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
601    }
602
603    @VisibleForTesting
604    AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
605        final int N = cache.size();
606        for (int i = 0; i < N; i++) {
607            if (cache.get(i).matches(result)) {
608                AccessPoint ret = cache.remove(i);
609                ret.update(result);
610                return ret;
611            }
612        }
613        final AccessPoint accessPoint = new AccessPoint(mContext, result);
614        accessPoint.setListener(mAccessPointListenerAdapter);
615        return accessPoint;
616    }
617
618    @VisibleForTesting
619    AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
620        final int N = cache.size();
621        for (int i = 0; i < N; i++) {
622            if (cache.get(i).matches(config)) {
623                AccessPoint ret = cache.remove(i);
624                ret.loadConfig(config);
625                return ret;
626            }
627        }
628        final AccessPoint accessPoint = new AccessPoint(mContext, config);
629        accessPoint.setListener(mAccessPointListenerAdapter);
630        return accessPoint;
631    }
632
633    private void updateNetworkInfo(NetworkInfo networkInfo) {
634        /* sticky broadcasts can call this when wifi is disabled */
635        if (!mWifiManager.isWifiEnabled()) {
636            mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
637            return;
638        }
639
640        if (networkInfo != null &&
641                networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
642            mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
643        } else {
644            mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
645        }
646
647        if (networkInfo != null) {
648            mLastNetworkInfo = networkInfo;
649        }
650
651        WifiConfiguration connectionConfig = null;
652        mLastInfo = mWifiManager.getConnectionInfo();
653        if (mLastInfo != null) {
654            connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
655        }
656
657        boolean updated = false;
658        boolean reorder = false; // Only reorder if connected AP was changed
659
660        synchronized (mLock) {
661            for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) {
662                AccessPoint ap = mInternalAccessPoints.get(i);
663                boolean previouslyConnected = ap.isActive();
664                if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
665                    updated = true;
666                    if (previouslyConnected != ap.isActive()) reorder = true;
667                }
668                if (ap.update(mScoreCache, false /* mNetworkScoringUiEnabled */)) {
669                    reorder = true;
670                    updated = true;
671                }
672            }
673
674            if (reorder) Collections.sort(mInternalAccessPoints);
675
676            if (updated) mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
677        }
678    }
679
680    /**
681     * Update all the internal access points rankingScores, badge and metering.
682     *
683     * <p>Will trigger a resort and notify listeners of changes if applicable.
684     *
685     * <p>Synchronized on {@link #mLock}.
686     */
687    private void updateNetworkScores() {
688        synchronized (mLock) {
689            boolean updated = false;
690            for (int i = 0; i < mInternalAccessPoints.size(); i++) {
691                if (mInternalAccessPoints.get(i).update(
692                        mScoreCache, false /* mNetworkScoringUiEnabled */)) {
693                    updated = true;
694                }
695            }
696            if (updated) {
697                Collections.sort(mInternalAccessPoints);
698                mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
699            }
700        }
701    }
702
703    private void updateWifiState(int state) {
704        mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
705    }
706
707    public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
708            boolean includeScans, boolean includePasspoints) {
709        WifiTracker tracker = new WifiTracker(context,
710                null, null, includeSaved, includeScans, includePasspoints);
711        tracker.forceUpdate();
712        tracker.copyAndNotifyListeners(false /*notifyListeners*/);
713        return tracker.getAccessPoints();
714    }
715
716    @VisibleForTesting
717    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
718        @Override
719        public void onReceive(Context context, Intent intent) {
720            String action = intent.getAction();
721
722            if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
723                mStaleScanResults = false;
724            }
725
726            if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
727                updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
728                        WifiManager.WIFI_STATE_UNKNOWN));
729            } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
730                    WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
731                    WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
732                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
733            } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
734                NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
735                        WifiManager.EXTRA_NETWORK_INFO);
736                mConnected.set(info.isConnected());
737
738                mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
739
740                mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
741                        .sendToTarget();
742                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
743            } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
744                NetworkInfo info =
745                        mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork());
746                mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
747                        .sendToTarget();
748            }
749        }
750    };
751
752    private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
753        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
754            if (network.equals(mWifiManager.getCurrentNetwork())) {
755                // We don't send a NetworkInfo object along with this message, because even if we
756                // fetch one from ConnectivityManager, it might be older than the most recent
757                // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
758                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
759            }
760        }
761    }
762
763    @VisibleForTesting
764    final class MainHandler extends Handler {
765        @VisibleForTesting static final int MSG_CONNECTED_CHANGED = 0;
766        @VisibleForTesting static final int MSG_WIFI_STATE_CHANGED = 1;
767        @VisibleForTesting static final int MSG_ACCESS_POINT_CHANGED = 2;
768        private static final int MSG_RESUME_SCANNING = 3;
769        private static final int MSG_PAUSE_SCANNING = 4;
770
771        public MainHandler(Looper looper) {
772            super(looper);
773        }
774
775        @Override
776        public void handleMessage(Message msg) {
777            if (mListener == null) {
778                return;
779            }
780            switch (msg.what) {
781                case MSG_CONNECTED_CHANGED:
782                    mListener.onConnectedChanged();
783                    break;
784                case MSG_WIFI_STATE_CHANGED:
785                    mListener.onWifiStateChanged(msg.arg1);
786                    break;
787                case MSG_ACCESS_POINT_CHANGED:
788                    copyAndNotifyListeners(true /*notifyListeners*/);
789                    mListener.onAccessPointsChanged();
790                    break;
791                case MSG_RESUME_SCANNING:
792                    if (mScanner != null) {
793                        mScanner.resume();
794                    }
795                    break;
796                case MSG_PAUSE_SCANNING:
797                    if (mScanner != null) {
798                        mScanner.pause();
799                    }
800                    break;
801            }
802        }
803
804        void removePendingMessages() {
805            removeMessages(MSG_ACCESS_POINT_CHANGED);
806            removeMessages(MSG_CONNECTED_CHANGED);
807            removeMessages(MSG_WIFI_STATE_CHANGED);
808            removeMessages(MSG_PAUSE_SCANNING);
809            removeMessages(MSG_RESUME_SCANNING);
810        }
811    }
812
813    private final class WorkHandler extends Handler {
814        private static final int MSG_UPDATE_ACCESS_POINTS = 0;
815        private static final int MSG_UPDATE_NETWORK_INFO = 1;
816        private static final int MSG_RESUME = 2;
817        private static final int MSG_UPDATE_WIFI_STATE = 3;
818
819        public WorkHandler(Looper looper) {
820            super(looper);
821        }
822
823        @Override
824        public void handleMessage(Message msg) {
825            synchronized (mLock) {
826                processMessage(msg);
827            }
828        }
829
830        @GuardedBy("mLock")
831        private void processMessage(Message msg) {
832            if (!mRegistered) return;
833
834            switch (msg.what) {
835                case MSG_UPDATE_ACCESS_POINTS:
836                    if (!mStaleScanResults) {
837                        updateAccessPointsLocked();
838                    }
839                    break;
840                case MSG_UPDATE_NETWORK_INFO:
841                    updateNetworkInfo((NetworkInfo) msg.obj);
842                    break;
843                case MSG_RESUME:
844                    handleResume();
845                    break;
846                case MSG_UPDATE_WIFI_STATE:
847                    if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
848                        if (mScanner != null) {
849                            // We only need to resume if mScanner isn't null because
850                            // that means we want to be scanning.
851                            mScanner.resume();
852                        }
853                    } else {
854                        mLastInfo = null;
855                        mLastNetworkInfo = null;
856                        if (mScanner != null) {
857                            mScanner.pause();
858                        }
859                    }
860                    mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
861                            .sendToTarget();
862                    break;
863            }
864        }
865
866        private void removePendingMessages() {
867            removeMessages(MSG_UPDATE_ACCESS_POINTS);
868            removeMessages(MSG_UPDATE_NETWORK_INFO);
869            removeMessages(MSG_RESUME);
870            removeMessages(MSG_UPDATE_WIFI_STATE);
871        }
872    }
873
874    @VisibleForTesting
875    class Scanner extends Handler {
876        static final int MSG_SCAN = 0;
877
878        private int mRetry = 0;
879
880        void resume() {
881            if (!hasMessages(MSG_SCAN)) {
882                sendEmptyMessage(MSG_SCAN);
883            }
884        }
885
886        void forceScan() {
887            removeMessages(MSG_SCAN);
888            sendEmptyMessage(MSG_SCAN);
889        }
890
891        void pause() {
892            mRetry = 0;
893            removeMessages(MSG_SCAN);
894        }
895
896        @VisibleForTesting
897        boolean isScanning() {
898            return hasMessages(MSG_SCAN);
899        }
900
901        @Override
902        public void handleMessage(Message message) {
903            if (message.what != MSG_SCAN) return;
904            if (mWifiManager.startScan()) {
905                mRetry = 0;
906            } else if (++mRetry >= 3) {
907                mRetry = 0;
908                if (mContext != null) {
909                    Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
910                }
911                return;
912            }
913            sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
914        }
915    }
916
917    /** A restricted multimap for use in constructAccessPoints */
918    private static class Multimap<K,V> {
919        private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
920        /** retrieve a non-null list of values with key K */
921        List<V> getAll(K key) {
922            List<V> values = store.get(key);
923            return values != null ? values : Collections.<V>emptyList();
924        }
925
926        void put(K key, V val) {
927            List<V> curVals = store.get(key);
928            if (curVals == null) {
929                curVals = new ArrayList<V>(3);
930                store.put(key, curVals);
931            }
932            curVals.add(val);
933        }
934    }
935
936    public interface WifiListener {
937        /**
938         * Called when the state of Wifi has changed, the state will be one of
939         * the following.
940         *
941         * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
942         * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
943         * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
944         * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
945         * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
946         * <p>
947         *
948         * @param state The new state of wifi.
949         */
950        void onWifiStateChanged(int state);
951
952        /**
953         * Called when the connection state of wifi has changed and isConnected
954         * should be called to get the updated state.
955         */
956        void onConnectedChanged();
957
958        /**
959         * Called to indicate the list of AccessPoints has been updated and
960         * getAccessPoints should be called to get the latest information.
961         */
962        void onAccessPointsChanged();
963    }
964
965    /**
966     * Helps capture notifications that were generated during AccessPoint modification. Used later
967     * on by {@link #copyAndNotifyListeners(boolean)} to send notifications.
968     */
969    private static class AccessPointListenerAdapter implements AccessPoint.AccessPointListener {
970        static final int AP_CHANGED = 1;
971        static final int LEVEL_CHANGED = 2;
972
973        final SparseIntArray mPendingNotifications = new SparseIntArray();
974
975        @Override
976        public void onAccessPointChanged(AccessPoint accessPoint) {
977            int type = mPendingNotifications.get(accessPoint.mId);
978            mPendingNotifications.put(accessPoint.mId, type | AP_CHANGED);
979        }
980
981        @Override
982        public void onLevelChanged(AccessPoint accessPoint) {
983            int type = mPendingNotifications.get(accessPoint.mId);
984            mPendingNotifications.put(accessPoint.mId, type | LEVEL_CHANGED);
985        }
986    }
987
988    /**
989     * Responsible for copying access points from {@link #mInternalAccessPoints} and notifying
990     * accesspoint listeners.
991     *
992     * @param notifyListeners if true, accesspoint listeners are notified, otherwise notifications
993     *                        dropped.
994     */
995    @MainThread
996    private void copyAndNotifyListeners(boolean notifyListeners) {
997        // Need to watch out for memory allocations on main thread.
998        SparseArray<AccessPoint> oldAccessPoints = new SparseArray<>();
999        SparseIntArray notificationMap = null;
1000        List<AccessPoint> updatedAccessPoints = new ArrayList<>();
1001
1002        for (AccessPoint accessPoint : mAccessPoints) {
1003            oldAccessPoints.put(accessPoint.mId, accessPoint);
1004        }
1005
1006        if (DBG) {
1007            Log.d(TAG, "Starting to copy AP items on the MainHandler");
1008        }
1009        synchronized (mLock) {
1010            if (notifyListeners) {
1011                notificationMap = mAccessPointListenerAdapter.mPendingNotifications.clone();
1012            }
1013
1014            mAccessPointListenerAdapter.mPendingNotifications.clear();
1015
1016            for (AccessPoint internalAccessPoint : mInternalAccessPoints) {
1017                AccessPoint accessPoint = oldAccessPoints.get(internalAccessPoint.mId);
1018                if (accessPoint == null) {
1019                    accessPoint = new AccessPoint(mContext, internalAccessPoint);
1020                } else {
1021                    accessPoint.copyFrom(internalAccessPoint);
1022                }
1023                updatedAccessPoints.add(accessPoint);
1024            }
1025        }
1026
1027        mAccessPoints.clear();
1028        mAccessPoints.addAll(updatedAccessPoints);
1029
1030        if (notificationMap != null && notificationMap.size() > 0) {
1031            for (AccessPoint accessPoint : updatedAccessPoints) {
1032                int notificationType = notificationMap.get(accessPoint.mId);
1033                AccessPoint.AccessPointListener listener = accessPoint.mAccessPointListener;
1034                if (notificationType == 0 || listener == null) {
1035                    continue;
1036                }
1037
1038                if ((notificationType & AccessPointListenerAdapter.AP_CHANGED) != 0) {
1039                    listener.onAccessPointChanged(accessPoint);
1040                }
1041
1042                if ((notificationType & AccessPointListenerAdapter.LEVEL_CHANGED) != 0) {
1043                    listener.onLevelChanged(accessPoint);
1044                }
1045            }
1046        }
1047    }
1048}
1049