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