1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.mozstumbler.service.stumblerthread.scanners;
6 
7 import android.content.BroadcastReceiver;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.IntentFilter;
11 import android.net.wifi.ScanResult;
12 import android.net.wifi.WifiManager;
13 import android.net.wifi.WifiManager.WifiLock;
14 import android.support.v4.content.LocalBroadcastManager;
15 import android.util.Log;
16 
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.Timer;
23 import java.util.TimerTask;
24 import java.util.concurrent.atomic.AtomicInteger;
25 
26 import org.mozilla.mozstumbler.service.AppGlobals;
27 import org.mozilla.mozstumbler.service.stumblerthread.blocklist.BSSIDBlockList;
28 import org.mozilla.mozstumbler.service.stumblerthread.blocklist.SSIDBlockList;
29 import org.mozilla.mozstumbler.service.AppGlobals.ActiveOrPassiveStumbling;
30 import org.mozilla.mozstumbler.service.Prefs;
31 import org.mozilla.mozstumbler.service.stumblerthread.blocklist.WifiBlockListInterface;
32 
33 public class WifiScanner extends BroadcastReceiver {
34     public static final String ACTION_BASE = AppGlobals.ACTION_NAMESPACE + ".WifiScanner.";
35     public static final String ACTION_WIFIS_SCANNED = ACTION_BASE + "WIFIS_SCANNED";
36     public static final String ACTION_WIFIS_SCANNED_ARG_RESULTS = "scan_results";
37     public static final String ACTION_WIFIS_SCANNED_ARG_TIME = AppGlobals.ACTION_ARG_TIME;
38 
39     public static final int STATUS_IDLE = 0;
40     public static final int STATUS_ACTIVE = 1;
41     public static final int STATUS_WIFI_DISABLED = -1;
42 
43     private static final String LOG_TAG = AppGlobals.makeLogTag(WifiScanner.class.getSimpleName());
44     private static final long WIFI_MIN_UPDATE_TIME = 5000; // milliseconds
45 
46     private boolean mStarted;
47     private final Context mContext;
48     private WifiLock mWifiLock;
49     private Timer mWifiScanTimer;
50     private final Set<String> mAPs = Collections.synchronizedSet(new HashSet<String>());
51     private final AtomicInteger mVisibleAPs = new AtomicInteger();
52 
53     /* Testing */
54     public static boolean sIsTestMode;
55     public List<ScanResult> mTestModeFakeScanResults = new ArrayList<ScanResult>();
getAccessPoints(android.test.AndroidTestCase restrictedAccessor)56     public Set<String> getAccessPoints(android.test.AndroidTestCase restrictedAccessor) { return mAPs; }
57     /* ------- */
58 
WifiScanner(Context c)59     public WifiScanner(Context c) {
60         mContext = c;
61     }
62 
isWifiEnabled()63     private boolean isWifiEnabled() {
64         return (sIsTestMode) || getWifiManager().isWifiEnabled();
65     }
66 
getScanResults()67     private List<ScanResult> getScanResults() {
68         WifiManager manager = getWifiManager();
69         if (manager == null) {
70             return null;
71         }
72         return getWifiManager().getScanResults();
73     }
74 
75 
start(final ActiveOrPassiveStumbling stumblingMode)76     public synchronized void start(final ActiveOrPassiveStumbling stumblingMode) {
77         Prefs prefs = Prefs.getInstanceWithoutContext();
78         if (mStarted || prefs == null) {
79             return;
80         }
81         mStarted = true;
82 
83         boolean scanAlways = prefs.getWifiScanAlways();
84 
85         if (scanAlways || isWifiEnabled()) {
86             activatePeriodicScan(stumblingMode);
87         }
88 
89         IntentFilter i = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
90         if (!scanAlways) i.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
91         mContext.registerReceiver(this, i);
92     }
93 
stop()94     public synchronized void stop() {
95         if (mStarted) {
96             mContext.unregisterReceiver(this);
97         }
98         deactivatePeriodicScan();
99         mStarted = false;
100     }
101 
102     @Override
onReceive(Context c, Intent intent)103     public void onReceive(Context c, Intent intent) {
104         String action = intent.getAction();
105 
106         if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
107             if (isWifiEnabled()) {
108                 activatePeriodicScan(ActiveOrPassiveStumbling.ACTIVE_STUMBLING);
109             } else {
110                 deactivatePeriodicScan();
111             }
112         } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
113             final List<ScanResult> scanResultList = getScanResults();
114             if (scanResultList == null) {
115                 return;
116             }
117             final ArrayList<ScanResult> scanResults = new ArrayList<ScanResult>();
118             for (ScanResult scanResult : scanResultList) {
119                 scanResult.BSSID = BSSIDBlockList.canonicalizeBSSID(scanResult.BSSID);
120                 if (shouldLog(scanResult)) {
121                     scanResults.add(scanResult);
122                     mAPs.add(scanResult.BSSID);
123                 }
124             }
125             mVisibleAPs.set(scanResults.size());
126             reportScanResults(scanResults);
127         }
128     }
129 
setWifiBlockList(WifiBlockListInterface blockList)130     public static void setWifiBlockList(WifiBlockListInterface blockList) {
131         BSSIDBlockList.setFilterList(blockList.getBssidOuiList());
132         SSIDBlockList.setFilterLists(blockList.getSsidPrefixList(), blockList.getSsidSuffixList());
133     }
134 
getAPCount()135     public int getAPCount() {
136         return mAPs.size();
137     }
138 
getVisibleAPCount()139     public int getVisibleAPCount() {
140         return mVisibleAPs.get();
141     }
142 
getStatus()143     public synchronized int getStatus() {
144         if (!mStarted) {
145             return STATUS_IDLE;
146         }
147         if (mWifiScanTimer == null) {
148             return STATUS_WIFI_DISABLED;
149         }
150         return STATUS_ACTIVE;
151     }
152 
activatePeriodicScan(final ActiveOrPassiveStumbling stumblingMode)153     private synchronized void activatePeriodicScan(final ActiveOrPassiveStumbling stumblingMode) {
154         if (mWifiScanTimer != null) {
155             return;
156         }
157 
158         if (AppGlobals.isDebug) {
159             Log.v(LOG_TAG, "Activate Periodic Scan");
160         }
161 
162         mWifiLock = getWifiManager().createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, "MozStumbler");
163         mWifiLock.acquire();
164 
165         // Ensure that we are constantly scanning for new access points.
166         mWifiScanTimer = new Timer();
167         mWifiScanTimer.schedule(new TimerTask() {
168             int mPassiveScanCount;
169             @Override
170             public void run() {
171                 if (stumblingMode == ActiveOrPassiveStumbling.PASSIVE_STUMBLING &&
172                     mPassiveScanCount++ > AppGlobals.PASSIVE_MODE_MAX_SCANS_PER_GPS)
173                 {
174                     mPassiveScanCount = 0;
175                     stop(); // set mWifiScanTimer to null
176                     return;
177                 }
178                 if (AppGlobals.isDebug) {
179                     Log.v(LOG_TAG, "WiFi Scanning Timer fired");
180                 }
181                 getWifiManager().startScan();
182             }
183         }, 0, WIFI_MIN_UPDATE_TIME);
184     }
185 
deactivatePeriodicScan()186     private synchronized void deactivatePeriodicScan() {
187         if (mWifiScanTimer == null) {
188             return;
189         }
190 
191         if (AppGlobals.isDebug) {
192             Log.v(LOG_TAG, "Deactivate periodic scan");
193         }
194 
195         mWifiLock.release();
196         mWifiLock = null;
197 
198         mWifiScanTimer.cancel();
199         mWifiScanTimer = null;
200 
201         mVisibleAPs.set(0);
202     }
203 
shouldLog(ScanResult scanResult)204     public static boolean shouldLog(ScanResult scanResult) {
205         if (BSSIDBlockList.contains(scanResult)) {
206             return false;
207         }
208         if (SSIDBlockList.contains(scanResult)) {
209             return false;
210         }
211         return true;
212     }
213 
getWifiManager()214     private WifiManager getWifiManager() {
215         return (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
216     }
217 
reportScanResults(ArrayList<ScanResult> scanResults)218     private void reportScanResults(ArrayList<ScanResult> scanResults) {
219         if (scanResults.isEmpty()) {
220             return;
221         }
222 
223         Intent i = new Intent(ACTION_WIFIS_SCANNED);
224         i.putParcelableArrayListExtra(ACTION_WIFIS_SCANNED_ARG_RESULTS, scanResults);
225         i.putExtra(ACTION_WIFIS_SCANNED_ARG_TIME, System.currentTimeMillis());
226         LocalBroadcastManager.getInstance(mContext).sendBroadcastSync(i);
227     }
228 }
229