Created
August 2, 2017 12:44
-
-
Save bojanpotocnik/56614829f9bd69f0d45f5a9a1b0594e9 to your computer and use it in GitHub Desktop.
WifiScanner with regex including/excluding and LiveData
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//package si.um.leis. | |
import android.arch.lifecycle.LiveData | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.Intent | |
import android.content.IntentFilter | |
import android.net.wifi.WifiManager | |
import android.os.Handler | |
import android.support.annotation.MainThread | |
import android.util.Log | |
import si.um.leis.ivent.App | |
import java.util.* | |
import java.util.regex.Pattern | |
/* | |
* Created on: 16. 05. 2017 | |
* Author: Laboratory for Electronic and Information Systems (LEIS) | |
* Bojan Potocnik | |
* [email protected] | |
*/ | |
/** | |
* LiveData singleton providing WiFi scanning capabilities. | |
*/ | |
object WifiScanner : LiveData<List<ScanResult>>() { | |
private const val TAG = "WifiScanner" | |
/** If `true`, (only) count of the found networks will be printed via [Log]. */ | |
private const val DEBUG_NETWORKS_COUNT = true | |
/** If `true`, all found networks (SSID and BSSID) will be printed via [Log]. */ | |
private const val DEBUG_NETWORKS_SSID = false | |
/** Application context used for getting system service and registering broadcast receiver. */ | |
private val applicationContext: Context by lazy { | |
App.applicationContext | |
} | |
/** [WifiManager] instance. */ | |
private val mWifiManager: WifiManager by lazy { | |
val manager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager | |
// Turn on WiFi if not yet enabled. | |
manager.isWifiEnabled = true | |
manager | |
} | |
/** Runnable (used by [mHandler]) for repeated scanning. */ | |
private val mRunnable by lazy { | |
Runnable { | |
if (mWifiManager.startScan()) { | |
mScanStartTime = System.currentTimeMillis() | |
} | |
} | |
} | |
/** Handler executing [mRunnable] for repeated scanning. */ | |
private val mHandler: Handler by lazy { | |
Handler() | |
} | |
/** Broadcast receiver for [Context.registerReceiver]. */ | |
private val mBroadcastReceiver: BroadcastReceiver by lazy { | |
object : BroadcastReceiver() { | |
@MainThread | |
override fun onReceive(context: Context?, intent: Intent?) = onScanResults(context, intent) | |
} | |
} | |
/** Discovered WiFi networks. */ | |
private val mNetworks: LinkedList<ScanResult> by lazy { LinkedList<ScanResult>() } | |
/** | |
* Regex masks for including SSIDs. | |
* | |
* See [configureScan]. | |
*/ | |
private var mSsidRegexIncludeFilters: Array<Pattern>? = null | |
/** | |
* Regex masks for excluding SSIDs. | |
* | |
* See [configureScan]. | |
*/ | |
private var mSsidRegexExcludeFilters: Array<Pattern>? = null | |
/** | |
* Scan repeat interval in milliseconds. | |
* | |
* See [configureScan]. | |
*/ | |
private var mRepeatInterval = 0 | |
/** Timestamp when last single WiFi scan was initiated. */ | |
private var mScanStartTime: Long = 0 | |
/** See [getLastScanDuration]. */ | |
private var mLastScanDuration: Int = -1 | |
/** | |
* If `true`, value will be updated with empty or the same list even if no or no new devices | |
* (after filtering) are found. | |
* | |
* See [configureScan]. | |
*/ | |
private var mUpdateIfNothingNew: Boolean = true | |
/** | |
* Configure WiFi networks scanning parameters. If scanning is already running, changes will | |
* be applied when current scan finished and it is restarted with new parameters. New filters | |
* will already be used on this scan result. | |
* | |
* @param ssidIncludeFilter Array of regex masks. If not `null`, SSID's will be compared to | |
* every regex string until matching one is found. If SSID does not match | |
* any of the provided SSID strings, it is ignored and not shown. | |
* See [Pattern (Java Class)](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html). | |
* @param ssidExcludeFilter The same as ssidIncludeFilter except if SSID matches this filter it | |
* will not be shown. This is done after filtering by ssidIncludeFilter. | |
* @param repeatInterval Scan repeat interval in milliseconds. If positive value, scan will be | |
* restarted every such amount of milliseconds (or later, if scan itself | |
* takes more time). If `0` negative, new scan will be initiated | |
* immediately after results of the previous request are received. | |
* If less than `0` or `null`, scan will be started immediately and only | |
* once. | |
* @param updateIfNothingNew If `true`, value will be updated with empty or the same list even | |
* if no or no new devices (after filtering) are found. | |
*/ | |
fun configureScan(ssidIncludeFilter: Array<String>? = null, | |
ssidExcludeFilter: Array<String>? = null, | |
repeatInterval: Int = 0, | |
updateIfNothingNew: Boolean = true) { | |
mSsidRegexIncludeFilters = | |
if (ssidIncludeFilter != null) | |
Array(ssidIncludeFilter.size) { index -> | |
Pattern.compile(ssidIncludeFilter[index]) | |
} | |
else null | |
mSsidRegexExcludeFilters = | |
if (ssidExcludeFilter != null) | |
Array(ssidExcludeFilter.size) { index -> | |
Pattern.compile(ssidExcludeFilter[index]) | |
} | |
else null | |
mRepeatInterval = repeatInterval | |
mUpdateIfNothingNew = updateIfNothingNew | |
mNetworks.clear() | |
// This is only configuration, do not start scanning here. | |
if (hasActiveObservers()) { | |
// Try to apply settings as soon as possible by force restarting the scan. | |
executeScan() | |
} | |
} | |
/** | |
* @return Time (in milliseconds) taken to perform one (last) scan of WiFi networks or -1 if no | |
* scan has been finished yet. | |
*/ | |
fun getLastScanDuration(): Int = mLastScanDuration | |
/** | |
* Execute or schedule delayed execution of the next scan. | |
*/ | |
private fun executeScan(delay: Long = 0) { | |
if (delay > 0) { | |
mHandler.postDelayed(mRunnable, delay) | |
} else { | |
// Start scan directly. | |
mRunnable.run() | |
} | |
} | |
/** | |
* Called when an [WifiManager.SCAN_RESULTS_AVAILABLE_ACTION] Intent broadcast is received. | |
*/ | |
@Suppress("UNUSED_PARAMETER") | |
@MainThread | |
private fun onScanResults(context: Context?, intent: Intent?) { | |
// Get all found networks. | |
val wifiList = mWifiManager.scanResults | |
// Measure time taken for the scan to finish. | |
mLastScanDuration = (System.currentTimeMillis() - mScanStartTime).toInt() | |
if (DEBUG_NETWORKS_COUNT) { | |
Log.d(TAG, "Scan finished in $mLastScanDuration ms with " + wifiList.size + " results.") | |
} | |
var newNetwork = false | |
var updatedNetwork = false | |
// Add found networks to the internal list of matching networks. | |
for (wifi in wifiList) { | |
var matches = false | |
// Filter networks, if filter is provided. | |
if (mSsidRegexIncludeFilters != null) { | |
mSsidRegexIncludeFilters!!.forEach { | |
if (it.matcher(wifi.SSID).matches()) { | |
matches = true | |
return@forEach | |
} | |
} | |
} else { | |
matches = true | |
} | |
// If network passes the include filter, check also for exclude filter. | |
if (matches && (mSsidRegexExcludeFilters != null)) { | |
mSsidRegexExcludeFilters!!.forEach { | |
if (it.matcher(wifi.SSID).matches()) { | |
matches = false | |
return@forEach | |
} | |
} | |
} | |
// Only add matching network or update if already in the list. | |
if (matches) { | |
var updated = false | |
mNetworks.forEach({ | |
if (it.isTheSameAs(wifi)) { | |
if (DEBUG_NETWORKS_SSID) { | |
Log.d(TAG, "Updating: " + wifi.SSID + "/" + wifi.BSSID) | |
} | |
it.sr = wifi | |
it.updated = true | |
updated = true | |
updatedNetwork = true | |
return@forEach | |
} | |
}) | |
if (!updated) { | |
if (DEBUG_NETWORKS_SSID) { | |
Log.d(TAG, "Adding new: " + wifi.SSID + "/" + wifi.BSSID) | |
} | |
mNetworks.add(ScanResult(wifi)) | |
newNetwork = true | |
} | |
} else { | |
if (DEBUG_NETWORKS_SSID) { | |
Log.v(TAG, "Ignoring: " + wifi.SSID + "/" + wifi.BSSID) | |
} | |
} | |
} | |
// Schedule new scan. | |
// Scan start time is always lower than current time, therefore the delay is always <= 0 if the mRepeatInterval is <= 0. | |
executeScan(mScanStartTime + mRepeatInterval - System.currentTimeMillis()) | |
// Notify observers. | |
if (newNetwork || updatedNetwork || mUpdateIfNothingNew) { | |
value = mNetworks | |
} | |
} | |
//---------------------------------------------------------------------------------------------- | |
//region LiveData | |
/** | |
* Called when the number of active observers change from 0 to 1 - LiveData is being used. | |
*/ | |
override fun onActive() { | |
// Register receiver which will receive scan result, but do not yet start scanning. | |
applicationContext.registerReceiver(mBroadcastReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) | |
// Start (repeatedly) scanning for WiFi networks. | |
executeScan() | |
} | |
/** | |
* Called when the number of active observers change from 1 to 0. | |
*/ | |
override fun onInactive() { | |
// Stop scanning for WiFi networks. | |
mHandler.removeCallbacks(mRunnable) | |
applicationContext.unregisterReceiver(mBroadcastReceiver) | |
} | |
//endregion LiveData | |
//---------------------------------------------------------------------------------------------- | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment