1d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk/*
2d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * Copyright (C) 2015 The Android Open Source Project
3d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk *
4d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * Licensed under the Apache License, Version 2.0 (the "License");
5d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * you may not use this file except in compliance with the License.
6d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * You may obtain a copy of the License at
7d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk *
8d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk *      http://www.apache.org/licenses/LICENSE-2.0
9d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk *
10d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * Unless required by applicable law or agreed to in writing, software
11d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * distributed under the License is distributed on an "AS IS" BASIS,
12d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * See the License for the specific language governing permissions and
14d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * limitations under the License.
15d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk */
16d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkpackage com.android.settingslib.wifi;
17d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
18d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.content.BroadcastReceiver;
19d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.content.Context;
20d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.content.Intent;
21d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.content.IntentFilter;
22ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colittiimport android.net.ConnectivityManager;
23ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colittiimport android.net.Network;
24ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colittiimport android.net.NetworkCapabilities;
25d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.net.NetworkInfo;
26d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.net.NetworkInfo.DetailedState;
27ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colittiimport android.net.NetworkRequest;
28d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.net.wifi.ScanResult;
29d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.net.wifi.WifiConfiguration;
30d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.net.wifi.WifiInfo;
31d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.net.wifi.WifiManager;
32d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.os.Handler;
3330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monkimport android.os.Looper;
34d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.os.Message;
35fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpandeimport android.util.Log;
36d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport android.widget.Toast;
37d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
38d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport com.android.internal.annotations.VisibleForTesting;
39d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport com.android.settingslib.R;
40d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
41d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport java.io.PrintWriter;
42d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport java.util.ArrayList;
43fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpandeimport java.util.Collection;
44d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport java.util.Collections;
45d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport java.util.HashMap;
46fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpandeimport java.util.Iterator;
47d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport java.util.List;
48fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpandeimport java.util.Map;
49d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkimport java.util.concurrent.atomic.AtomicBoolean;
50d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
51d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk/**
52d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk * Tracks saved or available wifi networks and their state.
53d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk */
54d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monkpublic class WifiTracker {
55d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private static final String TAG = "WifiTracker";
56fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande    private static final boolean DBG = false;
57d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
58d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /** verbose logging flag. this flag is set thru developer debugging options
59d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * and used so as to assist with in-the-field WiFi connectivity debugging  */
60d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public static int sVerboseLogging = 0;
61d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
62d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    // TODO: Allow control of this?
63d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    // Combo scans can take 5-6s to complete - set to 10s.
64d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
65d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
66d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private final Context mContext;
67d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private final WifiManager mWifiManager;
68d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private final IntentFilter mFilter;
69ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti    private final ConnectivityManager mConnectivityManager;
70ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti    private final NetworkRequest mNetworkRequest;
71ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti    private WifiTrackerNetworkCallback mNetworkCallback;
72d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
73d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private final AtomicBoolean mConnected = new AtomicBoolean(false);
74d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private final WifiListener mListener;
75d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private final boolean mIncludeSaved;
76d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private final boolean mIncludeScans;
77dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande    private final boolean mIncludePasspoints;
78d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
7930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    private final MainHandler mMainHandler;
8030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    private final WorkHandler mWorkHandler;
8130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
82d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private boolean mSavedNetworksExist;
83d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private boolean mRegistered;
84d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private ArrayList<AccessPoint> mAccessPoints = new ArrayList<>();
85fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande    private HashMap<String, Integer> mSeenBssids = new HashMap<>();
86fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande    private HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
87fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande    private Integer mScanId = 0;
88fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande    private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
89d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
90d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private NetworkInfo mLastNetworkInfo;
91d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private WifiInfo mLastInfo;
92d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
93d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    @VisibleForTesting
94d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    Scanner mScanner;
95d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
9630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    public WifiTracker(Context context, WifiListener wifiListener,
9730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            boolean includeSaved, boolean includeScans) {
9830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        this(context, wifiListener, null, includeSaved, includeScans);
99dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande    }
10030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
10130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
10230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            boolean includeSaved, boolean includeScans) {
10330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
10430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    }
10530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
10630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    public WifiTracker(Context context, WifiListener wifiListener,
10730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
10830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
10930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    }
11030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
11130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
11230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
11330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
114ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                context.getSystemService(WifiManager.class),
115ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                context.getSystemService(ConnectivityManager.class), Looper.myLooper());
116d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
117d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
118d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    @VisibleForTesting
11930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
12030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            boolean includeSaved, boolean includeScans, boolean includePasspoints,
121ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            WifiManager wifiManager, ConnectivityManager connectivityManager,
122ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            Looper currentLooper) {
123d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (!includeSaved && !includeScans) {
124d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            throw new IllegalArgumentException("Must include either saved or scans");
125d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
126d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mContext = context;
127bf3d1af3d8bd32f410aa5cc44ccdc1df593ef210Jason Monk        if (currentLooper == null) {
128bf3d1af3d8bd32f410aa5cc44ccdc1df593ef210Jason Monk            // When we aren't on a looper thread, default to the main.
129bf3d1af3d8bd32f410aa5cc44ccdc1df593ef210Jason Monk            currentLooper = Looper.getMainLooper();
130bf3d1af3d8bd32f410aa5cc44ccdc1df593ef210Jason Monk        }
1312b51cc30d523cce3609a498c466364caf3ce504eJason Monk        mMainHandler = new MainHandler(currentLooper);
13230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        mWorkHandler = new WorkHandler(
1332b51cc30d523cce3609a498c466364caf3ce504eJason Monk                workerLooper != null ? workerLooper : currentLooper);
134d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mWifiManager = wifiManager;
135d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mIncludeSaved = includeSaved;
136d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mIncludeScans = includeScans;
137dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande        mIncludePasspoints = includePasspoints;
138d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mListener = wifiListener;
139ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti        mConnectivityManager = connectivityManager;
140d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
141d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        // check if verbose logging has been turned on or off
142d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
143d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
144d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter = new IntentFilter();
145d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
146d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
147d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
148d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
149d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
150d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
151d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
152ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti
153ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti        mNetworkRequest = new NetworkRequest.Builder()
154ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                .clearCapabilities()
155ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
156ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                .build();
157d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
158d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
159d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
160d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Forces an update of the wifi networks when not scanning.
161d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
162d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public void forceUpdate() {
163d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        updateAccessPoints();
164d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
165d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
166d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
167d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Force a scan for wifi networks to happen now.
168d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
169d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public void forceScan() {
170d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (mWifiManager.isWifiEnabled() && mScanner != null) {
171d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mScanner.forceScan();
172d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
173d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
174d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
175d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
176d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Temporarily stop scanning for wifi networks.
177d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
178d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public void pauseScanning() {
179d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (mScanner != null) {
180d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mScanner.pause();
1816572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            mScanner = null;
182d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
183d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
184d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
185d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
186d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Resume scanning for wifi networks after it has been paused.
187d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
188d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public void resumeScanning() {
1896572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk        if (mScanner == null) {
1906572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            mScanner = new Scanner();
1916572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk        }
192fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande
193dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk        mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME);
194d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (mWifiManager.isWifiEnabled()) {
195d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mScanner.resume();
196d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
19730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
198d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
199d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
200d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
201d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Start tracking wifi networks.
202d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Registers listeners and starts scanning for wifi networks. If this is not called
203d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * then forceUpdate() must be called to populate getAccessPoints().
204d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
205d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public void startTracking() {
206d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        resumeScanning();
207d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (!mRegistered) {
208d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mContext.registerReceiver(mReceiver, mFilter);
209ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            // NetworkCallback objects cannot be reused. http://b/20701525 .
210ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            mNetworkCallback = new WifiTrackerNetworkCallback();
211ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
212d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mRegistered = true;
213d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
214d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
215d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
216d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
217d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Stop tracking wifi networks.
218d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Unregisters all listeners and stops scanning for wifi networks. This should always
219d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * be called when done with a WifiTracker (if startTracking was called) to ensure
220d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * proper cleanup.
221d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
222d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public void stopTracking() {
223d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (mRegistered) {
224a42ee81368f4ce469db5a787f0f3a3edd15d9931Jason Monk            mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
225a42ee81368f4ce469db5a787f0f3a3edd15d9931Jason Monk            mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_INFO);
226d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mContext.unregisterReceiver(mReceiver);
227ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
228d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mRegistered = false;
229d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
230d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        pauseScanning();
231d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
232d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
233d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
234d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * Gets the current list of access points.
235d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
236d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public List<AccessPoint> getAccessPoints() {
2371904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk        synchronized (mAccessPoints) {
2381904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk            return new ArrayList<>(mAccessPoints);
2391904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk        }
240d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
241d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
242d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public WifiManager getManager() {
243d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        return mWifiManager;
244d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
245d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
246d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public boolean isWifiEnabled() {
247d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        return mWifiManager.isWifiEnabled();
248d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
249d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
250d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /**
251d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * @return true when there are saved networks on the device, regardless
252d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     * of whether the WifiTracker is tracking saved networks.
253d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk     */
254d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public boolean doSavedNetworksExist() {
255d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        return mSavedNetworksExist;
256d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
257d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
258d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public boolean isConnected() {
259d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        return mConnected.get();
260d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
261d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
262d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public void dump(PrintWriter pw) {
263d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        pw.println("  - wifi tracker ------");
2641904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk        for (AccessPoint accessPoint : getAccessPoints()) {
265d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            pw.println("  " + accessPoint);
266d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
267d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
268d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
269dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk    private void handleResume() {
270dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk        mScanResultCache.clear();
271dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk        mSeenBssids.clear();
272dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk        mScanId = 0;
273dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk    }
274dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk
275fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande    private Collection<ScanResult> fetchScanResults() {
276fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        mScanId++;
277fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        final List<ScanResult> newResults = mWifiManager.getScanResults();
278fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        for (ScanResult newResult : newResults) {
2799df79043907870fd4c1bd30b460b138169b92bbfMitchell Wills            if (newResult.SSID == null || newResult.SSID.isEmpty()) {
2809df79043907870fd4c1bd30b460b138169b92bbfMitchell Wills                continue;
2819df79043907870fd4c1bd30b460b138169b92bbfMitchell Wills            }
282fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            mScanResultCache.put(newResult.BSSID, newResult);
283fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            mSeenBssids.put(newResult.BSSID, mScanId);
284fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        }
285fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande
286fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
287fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
288fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
289fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
290fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                    it.hasNext(); /* nothing */) {
291fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                Map.Entry<String, Integer> e = it.next();
292fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                if (e.getValue() < threshold) {
293fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                    ScanResult result = mScanResultCache.get(e.getKey());
294fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                    if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
295fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                    mScanResultCache.remove(e.getKey());
296fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                    it.remove();
297fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                }
298fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            }
299fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
300fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        }
301fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande
302fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        return mScanResultCache.values();
303fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande    }
304fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande
3055a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills    private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) {
3065a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
3075a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        if (configs != null) {
3085a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills            for (WifiConfiguration config : configs) {
3095a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills                if (mLastInfo != null && networkId == config.networkId &&
3105a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills                        !(config.selfAdded && config.numAssociation == 0)) {
3115a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills                    return config;
3125a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills                }
3135a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills            }
3145a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        }
3155a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        return null;
3165a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills    }
3175a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills
318d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private void updateAccessPoints() {
319d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        // Swap the current access points into a cached list.
3201904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk        List<AccessPoint> cachedAccessPoints = getAccessPoints();
32130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        ArrayList<AccessPoint> accessPoints = new ArrayList<>();
32230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
323d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        // Clear out the configs so we don't think something is saved when it isn't.
32430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        for (AccessPoint accessPoint : cachedAccessPoints) {
325d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            accessPoint.clearConfig();
326d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
327d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
328d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        /** Lookup table to more quickly update AccessPoints by only considering objects with the
329d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
330d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
331fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande        WifiConfiguration connectionConfig = null;
3325a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        if (mLastInfo != null) {
3335a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills            connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
3345a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        }
335d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
3360775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe        final Collection<ScanResult> results = fetchScanResults();
3370775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe
338d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
339d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (configs != null) {
340d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mSavedNetworksExist = configs.size() != 0;
341d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            for (WifiConfiguration config : configs) {
342d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                if (config.selfAdded && config.numAssociation == 0) {
343d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    continue;
344d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                }
34530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
346d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                if (mLastInfo != null && mLastNetworkInfo != null) {
347dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                    if (config.isPasspoint() == false) {
3485a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills                        accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
349dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                    }
350d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                }
351d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                if (mIncludeSaved) {
3520775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                    if (!config.isPasspoint() || mIncludePasspoints) {
3530775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                        // If saved network not present in scan result then set its Rssi to MAX_VALUE
3540775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                        boolean apFound = false;
3550775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                        for (ScanResult result : results) {
3560775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                            if (result.SSID.equals(accessPoint.getSsidStr())) {
3570775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                                apFound = true;
3580775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                                break;
3590775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                            }
3600775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                        }
3610775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                        if (!apFound) {
3620775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                            accessPoint.setRssi(Integer.MAX_VALUE);
3630775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                        }
36430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                        accessPoints.add(accessPoint);
3650775a98e2095224d4b1618a4739543ce6e7f32ecSanket Padawe                    }
366dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande
367dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                    if (config.isPasspoint() == false) {
3686980d12c5864941e68933705c1f15a102ac348cbJason Monk                        apMap.put(accessPoint.getSsidStr(), accessPoint);
369dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                    }
370d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                } else {
371d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    // If we aren't using saved networks, drop them into the cache so that
372d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    // we have access to their saved info.
37330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    cachedAccessPoints.add(accessPoint);
374d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                }
375d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
376d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
377d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
378d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (results != null) {
379d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            for (ScanResult result : results) {
380d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                // Ignore hidden and ad-hoc networks.
381d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                if (result.SSID == null || result.SSID.length() == 0 ||
382d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                        result.capabilities.contains("[IBSS]")) {
383d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    continue;
384d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                }
385d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
386d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                boolean found = false;
387d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
388d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    if (accessPoint.update(result)) {
389d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                        found = true;
390d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                        break;
391d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    }
392d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                }
393d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                if (!found && mIncludeScans) {
39430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
395d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    if (mLastInfo != null && mLastNetworkInfo != null) {
3965a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills                        accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
397d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    }
398fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande
399a0d929e505432a8c84f3899696c910db16bd73bfVinit Deshpande                    if (result.isPasspointNetwork()) {
400dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                        WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
401dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                        if (config != null) {
402dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                            accessPoint.update(config);
403dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                        }
404dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande                    }
405dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande
406fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande                    if (mLastInfo != null && mLastInfo.getBSSID() != null
407fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande                            && mLastInfo.getBSSID().equals(result.BSSID)
408fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande                            && connectionConfig != null && connectionConfig.isPasspoint()) {
409fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande                        /* This network is connected via this passpoint config */
410fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande                        /* SSID match is not going to work for it; so update explicitly */
411fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande                        accessPoint.update(connectionConfig);
412fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande                    }
413fc40600e30e281712f6c2fc899fc3894b8ad0050Vinit Deshpande
41430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    accessPoints.add(accessPoint);
4156980d12c5864941e68933705c1f15a102ac348cbJason Monk                    apMap.put(accessPoint.getSsidStr(), accessPoint);
416d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                }
417d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
418d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
419d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
420d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        // Pre-sort accessPoints to speed preference insertion
42130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        Collections.sort(accessPoints);
422fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande
423fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        // Log accesspoints that were deleted
424fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        if (DBG) Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
425fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        for (AccessPoint prevAccessPoint : mAccessPoints) {
426fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            if (prevAccessPoint.getSsid() == null) continue;
427fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            String prevSsid = prevAccessPoint.getSsidStr();
428fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            boolean found = false;
429fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            for (AccessPoint newAccessPoint : accessPoints) {
430fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid().equals(prevSsid)) {
431fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                    found = true;
432fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                    break;
433fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                }
434fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            }
435fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande            if (!found)
436fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande                if (DBG) Log.d(TAG, "Did not find " + prevSsid + " in this scan");
437fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        }
438fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande        if (DBG)  Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
439fcd4612f70bb22e11bf776c76afc45b03a5b026cVinit Deshpande
44030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        mAccessPoints = accessPoints;
44130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
442d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
443d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
4441904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk    private AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) {
44530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        final int N = cache.size();
446d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        for (int i = 0; i < N; i++) {
44730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            if (cache.get(i).matches(result)) {
44830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                AccessPoint ret = cache.remove(i);
449d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                ret.update(result);
450d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                return ret;
451d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
452d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
453d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        return new AccessPoint(mContext, result);
454d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
455d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
4561904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk    private AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) {
45730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        final int N = cache.size();
458d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        for (int i = 0; i < N; i++) {
45930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            if (cache.get(i).matches(config)) {
46030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                AccessPoint ret = cache.remove(i);
461d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                ret.loadConfig(config);
462d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                return ret;
463d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
464d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
465d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        return new AccessPoint(mContext, config);
466d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
467d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
468d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private void updateNetworkInfo(NetworkInfo networkInfo) {
469e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk        /* sticky broadcasts can call this when wifi is disabled */
470e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk        if (!mWifiManager.isWifiEnabled()) {
471e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk            mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
472e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk            return;
473e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk        }
474d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
475e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk        if (networkInfo != null &&
476e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
477e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk            mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
478e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk        } else {
479e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk            mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
480d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
481d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
482d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (networkInfo != null) {
483d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mLastNetworkInfo = networkInfo;
484d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
485d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
4865a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        WifiConfiguration connectionConfig = null;
487ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti        mLastInfo = mWifiManager.getConnectionInfo();
4885a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        if (mLastInfo != null) {
4895a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills            connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
4905a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills        }
4915a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills
492d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        boolean reorder = false;
493d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        for (int i = mAccessPoints.size() - 1; i >= 0; --i) {
4945a42db2e9c4ce5700a321b9c2aa22189598ceeafMitchell Wills            if (mAccessPoints.get(i).update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
495d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                reorder = true;
496d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
497d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
498d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        if (reorder) {
4991904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk            synchronized (mAccessPoints) {
5001904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk                Collections.sort(mAccessPoints);
5011904081c62eeb1d22c7572829ed0591de7a09af6Jason Monk            }
50230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
503d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
504d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
505d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
506d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private void updateWifiState(int state) {
507cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills        mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
508d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
509d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
510d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
511dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande            boolean includeScans, boolean includePasspoints) {
512dcf00c9d0f6f985aef186cfe5d7b63761e2f8104Vinit Deshpande        WifiTracker tracker = new WifiTracker(context,
51330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                null, null, includeSaved, includeScans, includePasspoints);
514d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        tracker.forceUpdate();
515d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        return tracker.getAccessPoints();
516d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
517d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
518d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    @VisibleForTesting
519d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
520d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        @Override
521d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        public void onReceive(Context context, Intent intent) {
522d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            String action = intent.getAction();
523d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
524d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
525d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                        WifiManager.WIFI_STATE_UNKNOWN));
526d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
527d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
528d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
52930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
530d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
531d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
532d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                        WifiManager.EXTRA_NETWORK_INFO);
533d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                mConnected.set(info.isConnected());
53430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
53530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED);
53630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
53730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
53830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
53930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                        .sendToTarget();
540d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
541d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
542d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    };
543d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
544ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti    private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
545ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
546ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            if (network.equals(mWifiManager.getCurrentNetwork())) {
547ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                // We don't send a NetworkInfo object along with this message, because even if we
548ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                // fetch one from ConnectivityManager, it might be older than the most recent
549ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
550ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
551ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti            }
552ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti        }
553ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti    }
554ab313f8443c7dade78b7ae53d18dba002a144425Lorenzo Colitti
55530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    private final class MainHandler extends Handler {
55630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        private static final int MSG_CONNECTED_CHANGED = 0;
55730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        private static final int MSG_WIFI_STATE_CHANGED = 1;
55830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        private static final int MSG_ACCESS_POINT_CHANGED = 2;
559e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk        private static final int MSG_RESUME_SCANNING = 3;
560e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk        private static final int MSG_PAUSE_SCANNING = 4;
56130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
5622b51cc30d523cce3609a498c466364caf3ce504eJason Monk        public MainHandler(Looper looper) {
5632b51cc30d523cce3609a498c466364caf3ce504eJason Monk            super(looper);
5642b51cc30d523cce3609a498c466364caf3ce504eJason Monk        }
5652b51cc30d523cce3609a498c466364caf3ce504eJason Monk
56630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        @Override
56730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        public void handleMessage(Message msg) {
56830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            if (mListener == null) {
56930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                return;
57030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            }
57130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            switch (msg.what) {
57230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                case MSG_CONNECTED_CHANGED:
57330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    mListener.onConnectedChanged();
57430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    break;
57530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                case MSG_WIFI_STATE_CHANGED:
57630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    mListener.onWifiStateChanged(msg.arg1);
57730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    break;
57830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                case MSG_ACCESS_POINT_CHANGED:
57930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    mListener.onAccessPointsChanged();
58030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    break;
581e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                case MSG_RESUME_SCANNING:
582e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                    if (mScanner != null) {
583e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                        mScanner.resume();
584e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                    }
585e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                    break;
586e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                case MSG_PAUSE_SCANNING:
587e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                    if (mScanner != null) {
588e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                        mScanner.pause();
589e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                    }
590e04ae8aced5d3e2cb8a3f6fd800e048bd5e572cfJason Monk                    break;
59130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            }
59230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        }
59330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    }
59430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
59530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    private final class WorkHandler extends Handler {
59630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        private static final int MSG_UPDATE_ACCESS_POINTS = 0;
59730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        private static final int MSG_UPDATE_NETWORK_INFO = 1;
598dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk        private static final int MSG_RESUME = 2;
599cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills        private static final int MSG_UPDATE_WIFI_STATE = 3;
60030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
60130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        public WorkHandler(Looper looper) {
60230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            super(looper);
60330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        }
60430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
60530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        @Override
60630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        public void handleMessage(Message msg) {
60730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            switch (msg.what) {
60830d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                case MSG_UPDATE_ACCESS_POINTS:
60930d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    updateAccessPoints();
61030d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    break;
61130d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                case MSG_UPDATE_NETWORK_INFO:
61230d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    updateNetworkInfo((NetworkInfo) msg.obj);
61330d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk                    break;
614dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk                case MSG_RESUME:
615dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk                    handleResume();
616dbd05a98e5ed8cd1a4678790df507d9741da5ba2Jason Monk                    break;
617cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                case MSG_UPDATE_WIFI_STATE:
618cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                    if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) {
619cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                        if (mScanner != null) {
620cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                            // We only need to resume if mScanner isn't null because
621cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                            // that means we want to be scanning.
622cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                            mScanner.resume();
623cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                        }
624cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                    } else {
625cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                        mLastInfo = null;
626cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                        mLastNetworkInfo = null;
627cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                        if (mScanner != null) {
628cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                            mScanner.pause();
629cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                        }
630cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                    }
631cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                    mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0)
632cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                            .sendToTarget();
633cf0875a9b69da9da5e16563eca33609e775d9195Mitchell Wills                    break;
63430d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk            }
63530d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk        }
63630d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk    }
63730d8004dbf1941d9ac8c5c7839c135a1b87a87fcJason Monk
638d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    @VisibleForTesting
639d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    class Scanner extends Handler {
6402b51cc30d523cce3609a498c466364caf3ce504eJason Monk        static final int MSG_SCAN = 0;
6416572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk
642d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        private int mRetry = 0;
643d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
644d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        void resume() {
6456572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            if (!hasMessages(MSG_SCAN)) {
6466572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk                sendEmptyMessage(MSG_SCAN);
647d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
648d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
649d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
650d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        void forceScan() {
6516572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            removeMessages(MSG_SCAN);
6526572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            sendEmptyMessage(MSG_SCAN);
653d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
654d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
655d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        void pause() {
656d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            mRetry = 0;
6576572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            removeMessages(MSG_SCAN);
6586572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk        }
6596572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk
6606572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk        @VisibleForTesting
6616572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk        boolean isScanning() {
6626572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            return hasMessages(MSG_SCAN);
663d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
664d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
665d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        @Override
666d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        public void handleMessage(Message message) {
6676572eae9d278e7199440aeae9a91f9418e8ab4afJason Monk            if (message.what != MSG_SCAN) return;
668d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            if (mWifiManager.startScan()) {
669d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                mRetry = 0;
670d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            } else if (++mRetry >= 3) {
671d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                mRetry = 0;
672d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                if (mContext != null) {
673d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                    Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
674d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                }
675d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                return;
676d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
677d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
678d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
679d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
680d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
681d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    /** A restricted multimap for use in constructAccessPoints */
682d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    private static class Multimap<K,V> {
683d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
684d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        /** retrieve a non-null list of values with key K */
685d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        List<V> getAll(K key) {
686d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            List<V> values = store.get(key);
687d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            return values != null ? values : Collections.<V>emptyList();
688d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
689d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
690d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        void put(K key, V val) {
691d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            List<V> curVals = store.get(key);
692d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            if (curVals == null) {
693d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                curVals = new ArrayList<V>(3);
694d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk                store.put(key, curVals);
695d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            }
696d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk            curVals.add(val);
697d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        }
698d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
699d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
700d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    public interface WifiListener {
701d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        /**
702d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * Called when the state of Wifi has changed, the state will be one of
703d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * the following.
704d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         *
705d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
706d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
707d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
708d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
709d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
710d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * <p>
711d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         *
712d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * @param state The new state of wifi.
713d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         */
714d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        void onWifiStateChanged(int state);
715d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
716d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        /**
717d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * Called when the connection state of wifi has changed and isConnected
718d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * should be called to get the updated state.
719d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         */
720d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        void onConnectedChanged();
721d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk
722d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        /**
723d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * Called to indicate the list of AccessPoints has been updated and
724d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         * getAccessPoints should be called to get the latest information.
725d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk         */
726d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk        void onAccessPointsChanged();
727d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk    }
728d52356aa5e82c7c5db61672bbe8d0f44861f3e59Jason Monk}
729