From 82275d1b44310e70b1c16d7d96d3ac040d5e8c97 Mon Sep 17 00:00:00 2001 From: Tom Janson Date: Tue, 10 Mar 2015 17:27:21 +0100 Subject: [PATCH] initial commit --- .gitignore | 6 + .idea/.name | 1 + .idea/compiler.xml | 23 ++ .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 5 + .idea/gradle.xml | 19 ++ .idea/misc.xml | 10 + .idea/modules.xml | 10 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + WifiLocationLogger.iml | 19 ++ app/.gitignore | 1 + app/app.iml | 97 ++++++ app/build.gradle | 33 ++ app/proguard-rules.pro | 17 + app/src/main/AndroidManifest.xml | 27 ++ app/src/main/assets/logback.xml | 52 ++++ .../wifilocationlogger/MainActivity.java | 293 ++++++++++++++++++ .../WifiBroadcastReceiver.java | 98 ++++++ app/src/main/res/layout/activity_main.xml | 103 ++++++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes app/src/main/res/values-v21/styles.xml | 5 + app/src/main/res/values-w820dp/dimens.xml | 6 + app/src/main/res/values/dimens.xml | 5 + app/src/main/res/values/strings.xml | 17 + app/src/main/res/values/styles.xml | 8 + build.gradle | 19 ++ gradle.properties | 18 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++++++++++ gradlew.bat | 90 ++++++ settings.gradle | 1 + 36 files changed, 1168 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 WifiLocationLogger.iml create mode 100644 app/.gitignore create mode 100644 app/app.iml create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/logback.xml create mode 100644 app/src/main/java/com/tomjanson/wifilocationlogger/MainActivity.java create mode 100644 app/src/main/java/com/tomjanson/wifilocationlogger/WifiBroadcastReceiver.java create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values-v21/styles.xml create mode 100644 app/src/main/res/values-w820dp/dimens.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afbdab3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..bf5d053 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +WifiLocationLogger \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..fe865d3 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9076de5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b3f074e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c80f219 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/WifiLocationLogger.iml b/WifiLocationLogger.iml new file mode 100644 index 0000000..0bb6048 --- /dev/null +++ b/WifiLocationLogger.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..a23bace --- /dev/null +++ b/app/app.iml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..1aa1f2c --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.2" + defaultConfig { + applicationId "com.tomjanson.wifilocationlogger" + minSdkVersion 19 + targetSdkVersion 21 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.google.android.gms:play-services:6.5.87' + compile('com.github.tony19:logback-android-classic:1.1.1-3') { + exclude module: 'android' + } + compile('com.github.tony19:logback-android-core:1.1.1-3') { + exclude module: 'android' + } + compile 'org.slf4j:slf4j-api:1.7.10' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ab370aa --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/macbook/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bc9c6ac --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/logback.xml b/app/src/main/assets/logback.xml new file mode 100644 index 0000000..1942a1f --- /dev/null +++ b/app/src/main/assets/logback.xml @@ -0,0 +1,52 @@ + + + + + + + + %logger{0} + + + [ %thread ] %msg%n + + + + + ${LOG_DIR}/${LOG_FILE}.csv + + %date{ISO8601};%msg%n + + + + + ${LOG_DIR}/${LOGFILE}.%d.csv + + + + + ${LOG_DIR}/${LOG_FILE}.debug.txt + + %date{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + ${LOG_DIR}/${LOGFILE}.%d.debug.txt + + 7 + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/tomjanson/wifilocationlogger/MainActivity.java b/app/src/main/java/com/tomjanson/wifilocationlogger/MainActivity.java new file mode 100644 index 0000000..2be2484 --- /dev/null +++ b/app/src/main/java/com/tomjanson/wifilocationlogger/MainActivity.java @@ -0,0 +1,293 @@ +package com.tomjanson.wifilocationlogger; + +import android.app.Activity; +import android.content.Context; +import android.content.IntentFilter; +import android.location.Location; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import android.net.wifi.WifiManager; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; +import com.google.android.gms.location.LocationListener; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationServices; + +import java.text.DateFormat; +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Location update code based on: + * https://developer.android.com/training/location/receive-location-updates.html + * Wifi scan code loosely based on: + * http://www.tutorialspoint.com/android/android_wi_fi.htm + * https://github.com/Skarbo/WifiMapper + */ + +public class MainActivity extends Activity implements + ConnectionCallbacks, OnConnectionFailedListener, LocationListener { + + // Logback loggers, see https://github.com/tony19/logback-android + Logger log; + Logger diskLog; + + // Location update intervals + // it seems the updates take at least 5s; setting it lower doesn't seem to work + static final long LOCATION_UPDATE_INTERVAL_MILLIS = 3000; + static final long FASTEST_LOCATION_UPDATE_INTERVAL_MILLIS = 1000; + + // Wifi scan delay (i.e., wait $delay between completion of scan and start of next scan) + static final long WIFI_SCAN_DELAY_MILLIS = 2000; + + // TODO: try different delays + + // Instance state Bundle keys, + // see https://developer.android.com/training/basics/activity-lifecycle/recreating.html + private final static String LOCATION_KEY = "location-key"; + private final static String LAST_LOCATION_UPDATE_TIME_STRING_KEY = "last-location-update-time-string-key"; + private final static String LAST_WIFI_SCAN_TIME_STRING_KEY = "last-wifi-scan-time-string-key"; + + // Used to access Fused Location API, + // see https://developer.android.com/google/play-services/location.html + private GoogleApiClient googleApiClient; + private LocationRequest locationRequest; + Location currentLocation; + + // Wifi scan stuff + static WifiManager wifiManager; + static IntentFilter wifiIntentFilter; + static WifiBroadcastReceiver wifiBroadcastReceiver; + + // toggles logging location+wifi to file + // debug logs may be created regardless of this + boolean logToFile = false; + + // UI Elements + Button loggingButton; + TextView locationTV; + TextView locationAccuracyTV; + TextView locationUpdateTV; + Date lastLocationUpdateTime; + TextView wifiTV; + EditText wifiFilterET; + TextView wifiUpdateTV; + String wifiListString; + Date lastWifiScanTime; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + log = LoggerFactory.getLogger(MainActivity.class); // for debugging + diskLog = LoggerFactory.getLogger("disk"); // for machine-readable log + log.debug("Started; " + Build.VERSION.RELEASE + ", " + Build.ID + ", " + Build.MODEL); + + setContentView(R.layout.activity_main); + + assignUiElements(); + + updateValuesFromBundle(savedInstanceState); + + buildGoogleApiClient(); + + initWifiScan(); + wifiManager.startScan(); + } + + private void assignUiElements() { + loggingButton = (Button) findViewById(R.id.loggingButton); + locationTV = (TextView) findViewById(R.id.locationTextView); + locationAccuracyTV = (TextView) findViewById(R.id.locationAccuracyTextView); + locationUpdateTV = (TextView) findViewById(R.id.locationUpdateTextView); + wifiTV = (TextView) findViewById(R.id.wifiTextView); + wifiFilterET = (EditText) findViewById(R.id.wifiFilterEditText); + wifiUpdateTV = (TextView) findViewById(R.id.wifiUpdateTextView); + } + + void updateUI() { + if (currentLocation != null) { + locationTV.setText(currentLocation.getLatitude() + ", " + currentLocation.getLongitude()); + locationAccuracyTV.setText(currentLocation.getAccuracy() + " m"); + } + if (lastLocationUpdateTime != null) { + locationUpdateTV.setText(DateFormat.getTimeInstance().format(lastLocationUpdateTime)); + } + if (lastWifiScanTime != null) { + wifiUpdateTV.setText(DateFormat.getTimeInstance().format(lastWifiScanTime)); + } + if (wifiListString != null) { + wifiTV.setText(wifiListString); + } + + if (logToFile) { + loggingButton.setText(R.string.logging_stop); + } else { + loggingButton.setText(R.string.logging_start); + } + } + + public void toggleLogging(View view) { + logToFile = !logToFile; + updateUI(); + } + + private void initWifiScan() { + wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + wifiBroadcastReceiver = new WifiBroadcastReceiver(this); + wifiIntentFilter = new IntentFilter(); + wifiIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + this.registerReceiver(wifiBroadcastReceiver, wifiIntentFilter); + log.debug("Registered wifiBroadcastReceiver"); + } + + private void updateValuesFromBundle(Bundle savedInstanceState) { + log.debug("Updating values from bundle"); + if (savedInstanceState != null) { + if (savedInstanceState.keySet().contains(LOCATION_KEY)) { + currentLocation = savedInstanceState.getParcelable(LOCATION_KEY); + } + if (savedInstanceState.keySet().contains(LAST_LOCATION_UPDATE_TIME_STRING_KEY)) { + lastLocationUpdateTime = new Date(savedInstanceState.getLong(LAST_LOCATION_UPDATE_TIME_STRING_KEY)); + } + if (savedInstanceState.keySet().contains(LAST_WIFI_SCAN_TIME_STRING_KEY)) { + lastWifiScanTime = new Date(savedInstanceState.getLong(LAST_WIFI_SCAN_TIME_STRING_KEY)); + } + updateUI(); + } + } + + synchronized void buildGoogleApiClient() { + log.trace("Building GoogleApiClient"); + googleApiClient = new GoogleApiClient.Builder(this) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .addApi(LocationServices.API) + .build(); + createLocationRequest(); + } + + void createLocationRequest() { + locationRequest = new LocationRequest(); + + // Sets the desired interval for active location updates. This interval is + // inexact. You may not receive updates at all if no location sources are available, or + // you may receive them slower than requested. You may also receive updates faster than + // requested if other applications are requesting location at a faster interval. + locationRequest.setInterval(LOCATION_UPDATE_INTERVAL_MILLIS); + + // Sets the fastest rate for active location updates. This interval is exact, and your + // application will never receive updates faster than this value. + locationRequest.setFastestInterval(FASTEST_LOCATION_UPDATE_INTERVAL_MILLIS); + + locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + } + + /** + * Requests location updates from the FusedLocationApi. + */ + void startLocationUpdates() { + // The final argument to {@code requestLocationUpdates()} is a LocationListener + // (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html). + LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this); + } + + void stopLocationUpdates() { + LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this); + } + + @Override + public void onLocationChanged(Location location) { + log.info("Location: {}", location); + currentLocation = location; + lastLocationUpdateTime = new Date(); + updateUI(); + } + + @Override + protected void onStart() { + super.onStart(); + googleApiClient.connect(); + } + + @Override + protected void onResume() { + super.onResume(); + if (googleApiClient.isConnected()) { + startLocationUpdates(); + } + this.registerReceiver(wifiBroadcastReceiver, wifiIntentFilter); + log.debug("Registered wifiBroadcastReceiver"); + wifiManager.startScan(); + } + + @Override + protected void onPause() { + super.onPause(); + this.unregisterReceiver(wifiBroadcastReceiver); + log.debug("Unregistered wifiBroadcastReceiver"); + if (googleApiClient.isConnected()) { + stopLocationUpdates(); + } + } + + @Override + protected void onStop() { + super.onStop(); + if (googleApiClient.isConnected()) { + googleApiClient.disconnect(); + } + } + + /** + * Runs when a GoogleApiClient object successfully connects. + */ + @Override + public void onConnected(Bundle connectionHint) { + log.info("Connected to GoogleApiClient"); + + if (currentLocation == null) { + currentLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient); + lastLocationUpdateTime = new Date(); + updateUI(); + } + + startLocationUpdates(); + } + + @Override + public void onConnectionFailed(ConnectionResult result) { + log.warn("Connection failed: ConnectionResult.getErrorCode() = {}", result.getErrorCode()); + } + + @Override + public void onConnectionSuspended(int cause) { + log.info("Connection suspended"); + googleApiClient.connect(); + } + /** + * Stores activity data in the Bundle. + */ + @Override + public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { + savedInstanceState.putParcelable(LOCATION_KEY, currentLocation); + if (lastLocationUpdateTime != null) { + savedInstanceState.putLong(LAST_LOCATION_UPDATE_TIME_STRING_KEY, lastLocationUpdateTime.getTime()); + } + if (lastWifiScanTime != null) { + savedInstanceState.putLong(LAST_WIFI_SCAN_TIME_STRING_KEY, lastWifiScanTime.getTime()); + } + super.onSaveInstanceState(savedInstanceState); + } +} diff --git a/app/src/main/java/com/tomjanson/wifilocationlogger/WifiBroadcastReceiver.java b/app/src/main/java/com/tomjanson/wifilocationlogger/WifiBroadcastReceiver.java new file mode 100644 index 0000000..e556bee --- /dev/null +++ b/app/src/main/java/com/tomjanson/wifilocationlogger/WifiBroadcastReceiver.java @@ -0,0 +1,98 @@ +package com.tomjanson.wifilocationlogger; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.wifi.ScanResult; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/* + * Receives Wifi scan result whenever WifiManager has them, + * updates `wifiListString` and `lastWifiScanTime`, + * logs location (and accuracy) and Wifis (SSID, BSSID, strength) to disk + */ +class WifiBroadcastReceiver extends BroadcastReceiver { + private final MainActivity m; + + private final Comparator RSSI_ORDER = + new Comparator() { + public int compare(ScanResult e1, ScanResult e2) { + return Integer.compare(e2.level, e1.level); + } + }; + + private static final String WIFI_SCAN_TIMER = "wifi-scan-timer"; + private static Timer wifiScanTimer; + + public WifiBroadcastReceiver(MainActivity m) { + this.m = m; + wifiScanTimer = new Timer(WIFI_SCAN_TIMER); + } + + @Override + public void onReceive(Context context, Intent intent) { + List scanResultList = MainActivity.wifiManager.getScanResults(); + m.lastWifiScanTime = new Date(); + + Collections.sort(scanResultList, RSSI_ORDER); + m.log.debug("Wifis: {}", scanResultList); + + String combined = ""; + Pattern filter = makeFilter(); + + for (ScanResult wifi : scanResultList) { + if (!filter.matcher(wifi.SSID).matches()) { + continue; + } + + combined += wifi.SSID + " [" + wifi.BSSID + "]" + ": " + wifi.level + "\n"; + log(wifi); + } + + m.wifiListString = combined; + m.updateUI(); + + // schedule next scan after short delay + wifiScanTimer.schedule(new TimerTask() { + @Override + public void run() { + MainActivity.wifiManager.startScan(); + } + }, MainActivity.WIFI_SCAN_DELAY_MILLIS); + } + + private void log(ScanResult wifi) { + if (m.logToFile) { + m.diskLog.info(m.currentLocation.getLatitude() + + ";" + m.currentLocation.getLongitude() + + ";" + m.currentLocation.getAccuracy() + + ";" + m.lastLocationUpdateTime.getTime() + + ";" + wifi.SSID + + ";" + wifi.BSSID + + ";" + wifi.level + + ";" + m.lastWifiScanTime.getTime()); + } + } + + private Pattern makeFilter() { + // if not a valid regular expression or empty, don't filter at all + String regexp = m.wifiFilterET.getText().toString(); + if (regexp.equals("")) { + regexp = ".*"; + } + try { + return Pattern.compile(regexp); + } catch (PatternSyntaxException ex) { + return Pattern.compile(".*"); + } + } + +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..d6ae3d0 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,103 @@ + + + + +