Skip to content

Instantly share code, notes, and snippets.

@bojanpotocnik
Created August 2, 2017 12:44
Show Gist options
  • Save bojanpotocnik/56614829f9bd69f0d45f5a9a1b0594e9 to your computer and use it in GitHub Desktop.
Save bojanpotocnik/56614829f9bd69f0d45f5a9a1b0594e9 to your computer and use it in GitHub Desktop.
WifiScanner with regex including/excluding and LiveData
//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
* bojan.potocnik@um.si
*/
/**
* 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