├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── GoogleMapsLocationMapper.png ├── LocationMapperLocations.png ├── LocationMapperSettings.png ├── README.md ├── default.properties ├── html ├── RenderLocation.html └── images │ ├── image.png │ ├── proximity_enter.png │ ├── proximity_exit.png │ └── shadow.png ├── proguard.cfg ├── res ├── drawable-hdpi │ └── icon.png ├── drawable-ldpi │ └── icon.png ├── drawable-mdpi │ └── icon.png ├── drawable │ └── ic_menu_preferences.png ├── layout │ ├── location_settings.xml │ ├── stored_locations.xml │ └── stored_locations_row_layout.xml ├── menu │ └── options_menu.xml └── values │ └── strings.xml └── src └── com └── shinetech └── android ├── LocationCursorAdapter.java ├── LocationDbAdapter.java ├── LocationListenerService.java ├── LocationMapperApplication.java ├── Preferences.java ├── ShowLocationSettingsActivity.java └── ShowStoredLocationActivity.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | bin/ 4 | gen/ 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | LocationMapper 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /GoogleMapsLocationMapper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/GoogleMapsLocationMapper.png -------------------------------------------------------------------------------- /LocationMapperLocations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/LocationMapperLocations.png -------------------------------------------------------------------------------- /LocationMapperSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/LocationMapperSettings.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Location Mapper 2 | ##Introduction 3 | Location Mapper is two application working together to visualise location data accuracy. The motivation is to get an in-depth view of the accuracy of the location data as reported from GPS, network provider, and proximity alerts. 4 | 5 | The main part is an Android application that records location data from GPS and network location provider as well as proximity alerts to a SQLite database. The data can then be emailed as a CSV file. The CSV file contains the latitude, longitude, accuracy, provider name, and time stamp for each location. 6 | 7 | The second part is an HTML page to draw the data onto a Google Map. Selectboxes for GPS provider, network provider, and proximity alerts narrow down then data. Each location is drawn on the map with a circle around it denoting the accuracy, and a label for the time stamp. 8 | 9 | ##Android Application 10 | The Android application records location data from GPS and network provider as well as proximity alerts in a database. It is set up as a service and runs in the background. 11 | A list of the data recorded can be seen when the application runs in the foreground. 12 | 13 | ![alt Android Location Mapper Locations List](http://github.com/marcfasel/LocationMapper/raw/master/LocationMapperLocations.png "Android Location Mapper Locations List") 14 | 15 | ###Settings 16 | The application can be configured via the settings dialog. 17 | 18 | ![alt Android Location Mapper Settings](http://github.com/marcfasel/LocationMapper/raw/master/LocationMapperSettings.png "Android Location Mapper Settings") 19 | 20 | For GPS- and network location providers the update interval and the update distance can be set. 21 | 22 | For proximity alerts a location can be stored, and a proximity radius can be set. 23 | 24 | To visualise the data the email export sends the data as a CSV file. This CSV data can be pasted into the Google Maps application for viewing. 25 | 26 | The settings dialog also has a button to clear the database. 27 | ##Google Maps Application 28 | The folder html/ contains the Google Maps app RenderLocation.html. 29 | 30 | ![alt Google Maps Location Mapper](http://github.com/marcfasel/LocationMapper/raw/master/GoogleMapsLocationMapper.png "Google Maps Location Mapper") 31 | 32 | On the page the CSV data emailed previously can be pasted into the text area. Select from network, GPS, or proximity alert data, then press Render to view the data drawn on the map. -------------------------------------------------------------------------------- /default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=Google Inc.:Google APIs:8 12 | -------------------------------------------------------------------------------- /html/RenderLocation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 18 | 19 | 221 | 222 | 223 |
224 |

Paste location data into this text box below, select the type of location data you want to see, and press the "Render" button. It's that easy!

225 |
226 |
227 | 255 |
256 |
257 | Show 258 | GPS
259 | Show 260 | network
261 | Show 262 | proximity alerts
263 | 265 |
266 |
267 | 268 | 269 | -------------------------------------------------------------------------------- /html/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/html/images/image.png -------------------------------------------------------------------------------- /html/images/proximity_enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/html/images/proximity_enter.png -------------------------------------------------------------------------------- /html/images/proximity_exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/html/images/proximity_exit.png -------------------------------------------------------------------------------- /html/images/shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/html/images/shadow.png -------------------------------------------------------------------------------- /proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/drawable/ic_menu_preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcfasel/LocationMapper/e56ea22e03b8490d07bd028cf4169616afb02930/res/drawable/ic_menu_preferences.png -------------------------------------------------------------------------------- /res/layout/location_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 12 | 13 | 15 | 17 | 19 | 20 | 21 | 23 | 25 | 27 | 28 | 29 | 31 | 34 | 35 | 37 | 39 | 41 | 42 | 43 | 45 | 49 | 50 | 52 | 55 | 58 | 61 | 64 | 67 | 68 | 70 | 73 | 76 | 79 | 82 | 85 | 86 | 88 | 92 | 94 | 95 | 97 | 100 | 101 | 103 | 106 | 109 | 112 | 115 | 118 | 119 | 121 | 124 | 127 | 130 | 133 | 136 | 137 | 139 | 142 | 143 | 145 | 148 | 151 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /res/layout/stored_locations.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 12 | 15 | 18 | 21 | 24 | 25 | 26 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /res/layout/stored_locations_row_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 11 | 13 | 15 | 16 | -------------------------------------------------------------------------------- /res/menu/options_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Location Mapper 4 | Settings 5 | 6 | -------------------------------------------------------------------------------- /src/com/shinetech/android/LocationCursorAdapter.java: -------------------------------------------------------------------------------- 1 | package com.shinetech.android; 2 | 3 | import java.text.DecimalFormat; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | import android.content.Context; 8 | import android.database.Cursor; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.SimpleCursorAdapter; 13 | import android.widget.TextView; 14 | 15 | public class LocationCursorAdapter extends SimpleCursorAdapter { 16 | private Cursor c; 17 | private Context context; 18 | 19 | private static final String TAG = LocationCursorAdapter.class.toString(); 20 | 21 | public LocationCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) { 22 | super(context, layout, c, from, to); 23 | this.c = c; 24 | this.context = context; 25 | } 26 | 27 | public View getView(int pos, View inView, ViewGroup parent) { 28 | View v = inView; 29 | if (v == null) { 30 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 31 | v = inflater.inflate(R.layout.stored_locations_row_layout, null); 32 | } 33 | 34 | this.c.moveToPosition(pos); 35 | String value = this.c.getString(this.c.getColumnIndex(LocationDbAdapter.KEY_NAME)); 36 | TextView view = (TextView) v.findViewById(R.id.locationname); 37 | view.setText(value); 38 | String latitude = new DecimalFormat("###.####").format(this.c.getDouble(this.c.getColumnIndex(LocationDbAdapter.KEY_LATITUDE))); 39 | view = (TextView) v.findViewById(R.id.locationlatitude); 40 | view.setText(latitude); 41 | String longitude = new DecimalFormat("###.####").format(this.c.getDouble(this.c.getColumnIndex(LocationDbAdapter.KEY_LONGITUDE))); 42 | view = (TextView) v.findViewById(R.id.locationlongitude); 43 | view.setText(longitude); 44 | value = new DecimalFormat("####").format(this.c.getLong(this.c.getColumnIndex(LocationDbAdapter.KEY_ACCURACY))); 45 | view = (TextView) v.findViewById(R.id.locationaccuracy); 46 | view.setText(value); 47 | value = new SimpleDateFormat("HH:mm:ss").format(new Date(this.c.getLong(this.c.getColumnIndex(LocationDbAdapter.KEY_TIME)))); 48 | view = (TextView) v.findViewById(R.id.locationtime); 49 | view.setText(value); 50 | return (v); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/com/shinetech/android/LocationDbAdapter.java: -------------------------------------------------------------------------------- 1 | package com.shinetech.android; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.database.SQLException; 7 | import android.database.sqlite.SQLiteDatabase; 8 | import android.database.sqlite.SQLiteOpenHelper; 9 | import android.location.Location; 10 | import android.util.Log; 11 | 12 | public class LocationDbAdapter { 13 | 14 | public static final String KEY_NAME = "name"; 15 | public static final String KEY_LATITUDE = "latitude"; 16 | public static final String KEY_LONGITUDE = "longitude"; 17 | public static final String KEY_ACCURACY = "accuracy"; 18 | public static final String KEY_TIME = "time"; 19 | public static final String KEY_ROWID = "_id"; 20 | 21 | private static final String TAG = "LocationDbAdapter"; 22 | private DatabaseHelper mDbHelper; 23 | private SQLiteDatabase mDb; 24 | 25 | private static final String DATABASE_CREATE = 26 | "create table locations (_id integer primary key autoincrement, " 27 | + "name text not null, latitude real not null, longitude real not null, accuracy integer not null, time integer not null);"; 28 | 29 | private static final String DATABASE_NAME = "data"; 30 | private static final String DATABASE_TABLE = "locations"; 31 | private static final int DATABASE_VERSION = 4; 32 | 33 | private final Context mCtx; 34 | 35 | private static class DatabaseHelper extends SQLiteOpenHelper { 36 | 37 | DatabaseHelper(Context context) { 38 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 39 | } 40 | 41 | @Override 42 | public void onCreate(SQLiteDatabase db) { 43 | 44 | db.execSQL(DATABASE_CREATE); 45 | } 46 | 47 | @Override 48 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 49 | Log.i(TAG, "Upgrading database from version " + oldVersion + " to " 50 | + newVersion + ", which will destroy all old data"); 51 | db.execSQL("DROP TABLE IF EXISTS locations"); 52 | onCreate(db); 53 | } 54 | } 55 | 56 | /** 57 | * Constructor - takes the context to allow the database to be 58 | * opened/created 59 | * 60 | * @param ctx the Context within which to work 61 | */ 62 | public LocationDbAdapter(Context ctx) { 63 | this.mCtx = ctx; 64 | } 65 | 66 | /** 67 | * Open the notes database. If it cannot be opened, try to create a new 68 | * instance of the database. If it cannot be created, throw an exception to 69 | * signal the failure 70 | * 71 | * @return this (self reference, allowing this to be chained in an 72 | * initialization call) 73 | * @throws SQLException if the database could be neither opened or created 74 | */ 75 | public LocationDbAdapter open() throws SQLException { 76 | mDbHelper = new DatabaseHelper(mCtx); 77 | mDb = mDbHelper.getWritableDatabase(); 78 | return this; 79 | } 80 | 81 | public void close() { 82 | mDbHelper.close(); 83 | } 84 | 85 | 86 | /** 87 | * Add new location. If the location is 88 | * successfully created return the new rowId, otherwise return 89 | * a -1 to indicate failure. 90 | * 91 | * @param location 92 | * @return rowId or -1 if failed 93 | */ 94 | public long addLocation(Location location) { 95 | Log.i(TAG, "Writing new location:"+location.toString()); 96 | ContentValues contentValues = new ContentValues(); 97 | contentValues.put(KEY_NAME, location.getProvider()); 98 | contentValues.put(KEY_LATITUDE, location.getLatitude()); 99 | contentValues.put(KEY_LONGITUDE, location.getLongitude()); 100 | contentValues.put(KEY_ACCURACY, location.getAccuracy()); 101 | contentValues.put(KEY_TIME, location.getTime()); 102 | 103 | return mDb.insert(DATABASE_TABLE, null, contentValues); 104 | } 105 | 106 | public int clearDatabase() { 107 | Log.i(TAG, "clearDatabase()"); 108 | return mDb.delete(DATABASE_TABLE, null, null); 109 | } 110 | 111 | public Cursor fetchAllLocations() { 112 | 113 | return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_NAME, KEY_LATITUDE, KEY_LONGITUDE, KEY_ACCURACY, 114 | KEY_TIME}, null, null, null, null, null); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/com/shinetech/android/LocationListenerService.java: -------------------------------------------------------------------------------- 1 | package com.shinetech.android; 2 | 3 | import java.util.List; 4 | 5 | import android.app.PendingIntent; 6 | import android.app.Service; 7 | import android.content.BroadcastReceiver; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.IntentFilter; 11 | import android.location.Location; 12 | import android.location.LocationListener; 13 | import android.location.LocationManager; 14 | import android.os.Bundle; 15 | import android.os.IBinder; 16 | import android.util.Log; 17 | import android.widget.Toast; 18 | 19 | public class LocationListenerService extends Service implements LocationListener { 20 | 21 | public static final int UPDATE_MESSAGE = 1; 22 | public static final String PROXIMTY_ALERT_INTENT = "com.shinetech.android.PROXIMTY_ALERT"; 23 | public static final String PREFERNCES_CHANGED_INTENT = "com.shinetech.android.PREFERENCES_CHANGED"; 24 | 25 | private static final String TAG = LocationListenerService.class.toString(); 26 | private LocationManager locationManager; 27 | private LocationDbAdapter dbAdapter; 28 | 29 | private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 30 | @Override 31 | public void onReceive(Context context, Intent intent) { 32 | Log.i(TAG, "Received intent " + intent.toString()); 33 | if (PREFERNCES_CHANGED_INTENT.equals(intent.getAction())) { 34 | requestLocationUpdates(); 35 | addProximityAlert(); 36 | } else if (PROXIMTY_ALERT_INTENT.equals(intent.getAction())) { 37 | handleProximityAlert(intent); 38 | } 39 | } 40 | 41 | private void handleProximityAlert(Intent intent) { 42 | Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); 43 | if (location == null) { 44 | location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); 45 | if (location == null) { 46 | // We'll just write an empty location 47 | location = new Location("prx"); 48 | } 49 | } 50 | 51 | if (intent.hasExtra(LocationManager.KEY_PROXIMITY_ENTERING)){ 52 | if (intent.getBooleanExtra(LocationManager.KEY_PROXIMITY_ENTERING, true)==true) { 53 | location.setProvider("prx_enter"); 54 | } else { 55 | location.setProvider("prx_exit"); 56 | } 57 | } 58 | addLocationToDB(location); 59 | } 60 | }; 61 | 62 | @Override 63 | public int onStartCommand(Intent intent, int flags, int startId) { 64 | Log.i(TAG, "onStartCommand()"); 65 | super.onStartCommand(intent, flags, startId); 66 | // We want this service to continue running until it is explicitly 67 | // stopped, so return sticky. 68 | return START_STICKY; 69 | } 70 | 71 | @Override 72 | public void onCreate() { 73 | Log.i(TAG, "onCreate()"); 74 | dbAdapter = new LocationDbAdapter(this); 75 | dbAdapter.open(); 76 | 77 | requestLocationUpdates(); 78 | addProximityAlert(); 79 | 80 | // TODO Normally this is declared in the Manifest 81 | IntentFilter intentFilter = new IntentFilter(); 82 | intentFilter.addAction("com.shinetech.android.PREFERENCES_CHANGED"); 83 | this.registerReceiver(this.broadcastReceiver, intentFilter); 84 | 85 | } 86 | 87 | @Override 88 | public void onDestroy() { 89 | Log.i(TAG, "onDestroy()"); 90 | super.onDestroy(); 91 | dbAdapter.close(); 92 | locationManager.removeUpdates(this); 93 | } 94 | 95 | @Override 96 | public IBinder onBind(Intent intent) { 97 | return null; 98 | } 99 | 100 | public void onLocationChanged(Location location) { 101 | addLocationToDB(location); 102 | } 103 | 104 | private void addLocationToDB(Location location) { 105 | Log.i(TAG, "Location changed: " + location.toString()); 106 | dbAdapter.addLocation(location); 107 | Intent intent = new Intent("com.shinetech.android.UPDATE_UI"); 108 | sendBroadcast(intent); 109 | Log.i(TAG, "UPDATE_UI intent broadcasted"); 110 | } 111 | 112 | public void onProviderEnabled(String provider) { 113 | Log.i(TAG, "Provider " + provider + " is now enabled."); 114 | } 115 | 116 | public void onProviderDisabled(String provider) { 117 | Log.i(TAG, "Provider " + provider + " is now disabled."); 118 | } 119 | 120 | public void onStatusChanged(String provider, int i, Bundle b) { 121 | Log.i(TAG, "Provider " + provider + " has changed status to " + i); 122 | } 123 | 124 | private void requestLocationUpdates() { 125 | int sampleDistance = ((LocationMapperApplication) getApplication()).getPreferences().getSampleDistance(); 126 | int sampleInterval = ((LocationMapperApplication) getApplication()).getPreferences().getSampleInterval() * 1000 * 60; 127 | Log.i(TAG, "Setting up location updates with sample distance " + sampleDistance + " m and sample interval " 128 | + sampleInterval + " ms."); 129 | 130 | this.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); 131 | locationManager.removeUpdates(this); 132 | List enabledProviders = this.locationManager.getProviders(true); 133 | for (String provider:enabledProviders){ 134 | Log.i(TAG, "Requesting location updates from provider " + provider); 135 | this.locationManager.requestLocationUpdates(provider, sampleInterval, sampleDistance, this); 136 | } 137 | } 138 | 139 | private void addProximityAlert() { 140 | Log.i(TAG, "addProximityAlert()"); 141 | int vicinityRadius = ((LocationMapperApplication) getApplication()).getPreferences().getVicinityRadius(); 142 | double latitude = ((LocationMapperApplication) getApplication()).getPreferences().getLocation().getLatitude(); 143 | double longitude = ((LocationMapperApplication) getApplication()).getPreferences().getLocation().getLongitude(); 144 | 145 | long expiration = -1; 146 | 147 | Intent intent = new Intent(PROXIMTY_ALERT_INTENT); 148 | PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 0, intent, 0); 149 | 150 | locationManager.addProximityAlert(latitude, longitude, vicinityRadius, expiration, proximityIntent); 151 | 152 | IntentFilter filter = new IntentFilter(PROXIMTY_ALERT_INTENT); 153 | registerReceiver(this.broadcastReceiver, filter); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/com/shinetech/android/LocationMapperApplication.java: -------------------------------------------------------------------------------- 1 | package com.shinetech.android; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | public class LocationMapperApplication extends Application { 7 | public static final String PREFS_NAME = "LocationMapperPreferences"; 8 | private static final String TAG = "LocationMapperApplication"; 9 | 10 | private Preferences preferences = null; 11 | 12 | @Override 13 | public void onCreate() { 14 | Log.i(TAG, "onCreate()"); 15 | super.onCreate(); 16 | preferences = new Preferences(getSharedPreferences(PREFS_NAME, 0)); 17 | preferences.load(); 18 | } 19 | 20 | public Preferences getPreferences() { 21 | return preferences; 22 | } 23 | 24 | public void setPreferences(Preferences preferences) { 25 | this.preferences = preferences; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/com/shinetech/android/Preferences.java: -------------------------------------------------------------------------------- 1 | package com.shinetech.android; 2 | 3 | import android.content.SharedPreferences; 4 | import android.location.Location; 5 | import android.util.Log; 6 | 7 | public class Preferences { 8 | 9 | private static final String TAG = Preferences.class.toString(); 10 | 11 | private Location location = null; 12 | private int vicinityRadius; 13 | private int sampleInterval; 14 | private int sampleDistance; 15 | 16 | private SharedPreferences sharedPreferences; 17 | 18 | private Preferences() { 19 | // Private CTOR: Can't use this 20 | } 21 | 22 | public Preferences(SharedPreferences sharedPreferences) { 23 | this.sharedPreferences = sharedPreferences; 24 | } 25 | 26 | public Location getLocation() { 27 | return location; 28 | } 29 | 30 | public void setLocation(Location location) { 31 | this.location = location; 32 | } 33 | 34 | public int getVicinityRadius() { 35 | return vicinityRadius; 36 | } 37 | 38 | public void setVicinityRadius(int vicinityRadius) { 39 | this.vicinityRadius = vicinityRadius; 40 | } 41 | 42 | public int getSampleInterval() { 43 | return sampleInterval; 44 | } 45 | 46 | public void setSampleInterval(int sampleInterval) { 47 | this.sampleInterval = sampleInterval; 48 | } 49 | 50 | public int getSampleDistance() { 51 | return sampleDistance; 52 | } 53 | 54 | public void setSampleDistance(int sampleDistance) { 55 | this.sampleDistance = sampleDistance; 56 | } 57 | 58 | public void load() { 59 | Log.i(TAG, "load()"); 60 | // Only fetch preferences if they exist 61 | this.location = new Location(sharedPreferences.getString("name", "")); 62 | // TODO: These are actually doubles not floats 63 | this.location.setLatitude(sharedPreferences.getFloat("latitude", 0f)); 64 | this.location.setLongitude(sharedPreferences.getFloat("longitude", 0f)); 65 | this.location.setAccuracy(sharedPreferences.getFloat("accuracy", 0f)); 66 | this.location.setTime(sharedPreferences.getLong("time", 0l)); 67 | 68 | this.vicinityRadius = sharedPreferences.getInt("vicinityradius", 0); 69 | this.sampleInterval = sharedPreferences.getInt("sampleinterval", 0); 70 | this.sampleDistance = sharedPreferences.getInt("sampledistance", 0); 71 | } 72 | 73 | public void store() { 74 | Log.i(TAG, "store()"); 75 | SharedPreferences.Editor editor = sharedPreferences.edit(); 76 | editor.putString("name", location.getProvider()); 77 | editor.putFloat("latitude", (float) location.getLatitude()); 78 | editor.putFloat("longitude", (float) location.getLongitude()); 79 | editor.putFloat("accuracy", location.getAccuracy()); 80 | editor.putLong("time", location.getTime()); 81 | editor.putInt("vicinityradius", vicinityRadius); 82 | editor.putInt("sampleinterval", sampleInterval); 83 | editor.putInt("sampledistance", sampleDistance); 84 | 85 | editor.commit(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/com/shinetech/android/ShowLocationSettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.shinetech.android; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.io.OutputStreamWriter; 9 | import java.text.DecimalFormat; 10 | import java.text.SimpleDateFormat; 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | import org.apache.http.client.ClientProtocolException; 16 | import org.apache.http.client.HttpClient; 17 | import org.apache.http.client.methods.HttpPost; 18 | import org.apache.http.entity.InputStreamEntity; 19 | import org.apache.http.impl.client.DefaultHttpClient; 20 | 21 | import android.app.Activity; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.content.pm.ActivityInfo; 25 | import android.database.Cursor; 26 | import android.location.Location; 27 | import android.location.LocationManager; 28 | import android.net.Uri; 29 | import android.os.AsyncTask; 30 | import android.os.Bundle; 31 | import android.os.Environment; 32 | import android.util.Log; 33 | import android.view.View; 34 | import android.widget.EditText; 35 | import android.widget.TextView; 36 | import android.widget.Toast; 37 | 38 | public class ShowLocationSettingsActivity extends Activity { 39 | 40 | private static final int DELTA_MINUTES = 1000 * 60 * 5; 41 | public static final String PREFS_NAME = "LocationTrackerPreferences"; 42 | 43 | private static final String TAG = "ShowLocationSettingsActivity"; 44 | private LocationDbAdapter dbAdapter; 45 | private LocationManager locationManager; 46 | private Location currentLocation = null; 47 | private Location storedLocation = null; 48 | private Context context = null; 49 | 50 | @Override 51 | public void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | setContentView(R.layout.location_settings); 54 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 55 | 56 | this.locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); 57 | this.context = this; 58 | 59 | dbAdapter = new LocationDbAdapter(this); 60 | 61 | setCurrentLocation(); 62 | 63 | restorePreferences(); 64 | 65 | } 66 | 67 | @Override 68 | protected void onPause() { 69 | Log.i(TAG, "onPause"); 70 | super.onPause(); 71 | 72 | EditText editText = (EditText) findViewById(R.id.vicinityradius); 73 | int vicinityRadius = Integer.parseInt(editText.getText().toString()); 74 | 75 | editText = (EditText) findViewById(R.id.sampleinterval); 76 | int sampleInterval = Integer.parseInt(editText.getText().toString()); 77 | 78 | editText = (EditText) findViewById(R.id.sampledistance); 79 | int sampleDistance = Integer.parseInt(editText.getText().toString()); 80 | 81 | storePreferences(storedLocation, vicinityRadius, sampleInterval, sampleDistance); 82 | 83 | dbAdapter.close(); 84 | 85 | // Inform the LocationListenerService about possible changes 86 | Intent intent = new Intent("com.shinetech.android.PREFERENCES_CHANGED"); 87 | sendBroadcast(intent); 88 | } 89 | 90 | @Override 91 | protected void onResume() { 92 | Log.i(TAG, "onResume"); 93 | super.onResume(); 94 | dbAdapter.open(); 95 | } 96 | 97 | public void onClearDataBase(View view) { 98 | Log.i(TAG, "onClearDataBase"); 99 | int rows = dbAdapter.clearDatabase(); 100 | Toast.makeText(this, "Deleted " + rows + " rows.", Toast.LENGTH_SHORT).show(); 101 | } 102 | 103 | private void setCurrentLocation() { 104 | Location location = findLastLocation(); 105 | if (null != location) { 106 | currentLocation = location; 107 | TextView view = (TextView) findViewById(R.id.currentlocationname); 108 | view.setText(location.getProvider()); 109 | view = (TextView) findViewById(R.id.currentlocationlatitude); 110 | view.setText(new DecimalFormat("###.####").format(location.getLatitude())); 111 | view = (TextView) findViewById(R.id.currentlocationlongitude); 112 | view.setText(new DecimalFormat("###.####").format(location.getLongitude())); 113 | view = (TextView) findViewById(R.id.currentlocationtime); 114 | view.setText(new SimpleDateFormat("HH:mm:ss").format(new Date(location.getTime()))); 115 | view = (TextView) findViewById(R.id.currentlocationaccuracy); 116 | view.setText(new DecimalFormat("####").format(location.getAccuracy())); 117 | } 118 | } 119 | 120 | private void setStoredLocation(Location location) { 121 | 122 | storedLocation = location; 123 | 124 | TextView view = (TextView) findViewById(R.id.storedlocationname); 125 | view.setText(location.getProvider()); 126 | view = (TextView) findViewById(R.id.storedlocationlatitude); 127 | view.setText(new DecimalFormat("###.####").format(location.getLatitude())); 128 | view = (TextView) findViewById(R.id.storedlocationlongitude); 129 | view.setText(new DecimalFormat("###.####").format(location.getLongitude())); 130 | view = (TextView) findViewById(R.id.storedlocationtime); 131 | view.setText(new SimpleDateFormat("HH:mm:ss").format(new Date(location.getTime()))); 132 | view = (TextView) findViewById(R.id.storedlocationaccuracy); 133 | view.setText(new DecimalFormat("####").format(location.getAccuracy())); 134 | } 135 | 136 | private Location findLastLocation() { 137 | long minTime = new Date().getTime() - DELTA_MINUTES; // The last 5 138 | // minutes 139 | Location bestResult = null; 140 | long bestTime = Long.MAX_VALUE; 141 | float bestAccuracy = Float.MAX_VALUE; 142 | 143 | List matchingProviders = locationManager.getAllProviders(); 144 | for (String provider : matchingProviders) { 145 | Location location = locationManager.getLastKnownLocation(provider); 146 | if (location != null) { 147 | float accuracy = location.getAccuracy(); 148 | long time = location.getTime(); 149 | Log.i(TAG, "TIME= " + time + ", minTime= " + minTime + ", bestTime= " + bestTime + ", accuracy= " 150 | + accuracy + ", bestAccuracy= " + bestAccuracy); 151 | if ((time > minTime && accuracy < bestAccuracy)) { 152 | bestResult = location; 153 | bestAccuracy = accuracy; 154 | bestTime = time; 155 | } else if (time < minTime && bestAccuracy == Float.MAX_VALUE && time > bestTime) { 156 | bestResult = location; 157 | bestTime = time; 158 | } 159 | } 160 | } 161 | return bestResult; 162 | } 163 | 164 | private void restorePreferences() { 165 | Log.i(TAG, "restorePreferences"); 166 | Preferences preferences = ((LocationMapperApplication) getApplicationContext()).getPreferences(); 167 | 168 | setStoredLocation(preferences.getLocation()); 169 | 170 | EditText view = (EditText) findViewById(R.id.vicinityradius); 171 | view.setText(Integer.toString(preferences.getVicinityRadius())); 172 | 173 | view = (EditText) findViewById(R.id.sampleinterval); 174 | view.setText(Integer.toString(preferences.getSampleInterval())); 175 | 176 | view = (EditText) findViewById(R.id.sampledistance); 177 | view.setText(Integer.toString(preferences.getSampleDistance())); 178 | } 179 | 180 | private void storePreferences(Location location, int vicinityRadius, int sampleInterval, int sampleDistance) { 181 | Log.i(TAG, "storePreferences"); 182 | 183 | Preferences preferences = ((LocationMapperApplication) getApplicationContext()).getPreferences(); 184 | preferences.setLocation(location); 185 | preferences.setVicinityRadius(vicinityRadius); 186 | preferences.setSampleDistance(sampleDistance); 187 | preferences.setSampleInterval(sampleInterval); 188 | preferences.store(); 189 | } 190 | 191 | public void onStoreLocation(View view) { 192 | Log.i(TAG, "onStoreLocation"); 193 | storedLocation = currentLocation; 194 | storedLocation.setProvider("stored"); 195 | 196 | dbAdapter.addLocation(storedLocation); 197 | 198 | setStoredLocation(storedLocation); 199 | } 200 | 201 | public void onEmailData(View view) { 202 | Log.i(TAG, "onEmailData()"); 203 | EmailDataTask emailDataTask = new EmailDataTask(); 204 | emailDataTask.execute(); 205 | } 206 | 207 | 208 | private class EmailDataTask extends AsyncTask { 209 | @Override 210 | protected String doInBackground(String... urls) { 211 | try { 212 | writeDbDataToTempFile(); 213 | emailData(); 214 | } catch (Exception e) { 215 | Log.i(TAG, "Error emailing data: " + e.getMessage()); 216 | 217 | return "Error emailing data: " + e.getMessage(); 218 | } 219 | return "Data successfully emailed."; 220 | } 221 | 222 | @Override 223 | protected void onPostExecute(String result) { 224 | Log.i(TAG, "result"); 225 | } 226 | 227 | private void emailData() { 228 | final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE); 229 | emailIntent.setType("plain/text"); 230 | emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Location Mapper Data"); 231 | emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Location Mapper Data"); 232 | 233 | ArrayList uris = new ArrayList(); 234 | 235 | File fileIn = new File(Environment.getExternalStorageDirectory(),"data.csv"); 236 | 237 | Uri u = Uri.fromFile(fileIn); 238 | uris.add(u); 239 | emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); 240 | context.startActivity(Intent.createChooser(emailIntent, "Send mail...")); 241 | } 242 | 243 | private void writeDbDataToTempFile() throws IOException { 244 | Log.i(TAG, "writeDbDataToTempFile"); 245 | String data = ""; 246 | File dataFile = new File(Environment.getExternalStorageDirectory(),"data.csv"); 247 | FileOutputStream fileOutputStream = new FileOutputStream(dataFile); 248 | OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream); 249 | 250 | Cursor cursor = dbAdapter.fetchAllLocations(); 251 | cursor.moveToFirst(); 252 | 253 | Log.i(TAG, "Writing " + cursor.getCount() + " records."); 254 | while (cursor.isAfterLast() == false) { 255 | data = cursor.getDouble(2) + "," + cursor.getDouble(3) + "," + cursor.getInt(4) + "," 256 | + cursor.getString(1) + "," 257 | + new SimpleDateFormat("HH:mm:ss").format(new Date(cursor.getLong(5))) + "\n"; 258 | Log.i(TAG, data); 259 | outputStreamWriter.write(data); 260 | cursor.moveToNext(); 261 | } 262 | outputStreamWriter.flush(); 263 | outputStreamWriter.close(); 264 | cursor.close(); 265 | Log.i(TAG, "Done."); 266 | } 267 | 268 | } 269 | 270 | 271 | } 272 | -------------------------------------------------------------------------------- /src/com/shinetech/android/ShowStoredLocationActivity.java: -------------------------------------------------------------------------------- 1 | package com.shinetech.android; 2 | 3 | import android.app.ListActivity; 4 | import android.content.BroadcastReceiver; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.pm.ActivityInfo; 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.widget.SimpleCursorAdapter; 15 | 16 | public class ShowStoredLocationActivity extends ListActivity { 17 | private static final String TAG = "LocationTrackerActivity"; 18 | 19 | private LocationDbAdapter dbAdapter; 20 | private SimpleCursorAdapter cursorAdapter; 21 | 22 | public SimpleCursorAdapter getCursorAdapter() { 23 | return cursorAdapter; 24 | } 25 | 26 | public void setCursorAdapter(SimpleCursorAdapter cursorAdapter) { 27 | this.cursorAdapter = cursorAdapter; 28 | } 29 | 30 | private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 31 | @Override 32 | public void onReceive(Context context, Intent intent) { 33 | Log.i(TAG, "Received UPDATE_UI message"); 34 | getCursorAdapter().getCursor().requery(); 35 | getCursorAdapter().notifyDataSetChanged(); 36 | } 37 | }; 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.stored_locations); 43 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 44 | 45 | dbAdapter = new LocationDbAdapter(this); 46 | 47 | ComponentName locationListenerServiceName = new ComponentName(getPackageName(), 48 | LocationListenerService.class.getName()); 49 | startService(new Intent().setComponent(locationListenerServiceName)); 50 | } 51 | 52 | @Override 53 | public void onResume() { 54 | Log.i(TAG, "onResume()"); 55 | super.onResume(); 56 | dbAdapter.open(); 57 | String[] from = { LocationDbAdapter.KEY_NAME, LocationDbAdapter.KEY_LATITUDE, LocationDbAdapter.KEY_LONGITUDE, 58 | LocationDbAdapter.KEY_ACCURACY, LocationDbAdapter.KEY_TIME }; 59 | int[] to = { R.id.locationname, R.id.locationlatitude, R.id.locationlongitude, R.id.locationaccuracy, 60 | R.id.locationtime }; 61 | cursorAdapter = new LocationCursorAdapter(this, R.layout.stored_locations_row_layout, 62 | dbAdapter.fetchAllLocations(), from, to); 63 | this.setListAdapter(cursorAdapter); 64 | IntentFilter intentFilter = new IntentFilter(); 65 | intentFilter.addAction("com.shinetech.android.UPDATE_UI"); 66 | this.registerReceiver(this.broadcastReceiver, intentFilter); 67 | getCursorAdapter().getCursor().requery(); 68 | getCursorAdapter().notifyDataSetChanged(); 69 | } 70 | 71 | @Override 72 | public void onPause() { 73 | Log.i(TAG, "onPause()"); 74 | super.onPause(); 75 | dbAdapter.close(); 76 | this.unregisterReceiver(this.broadcastReceiver); 77 | } 78 | 79 | @Override 80 | public boolean onCreateOptionsMenu(Menu menu) { 81 | getMenuInflater().inflate(R.menu.options_menu, menu); 82 | return true; 83 | } 84 | 85 | @Override 86 | public boolean onOptionsItemSelected(MenuItem item) { 87 | switch (item.getItemId()) { 88 | case R.id.settings: 89 | startActivity(new Intent(this, ShowLocationSettingsActivity.class)); 90 | return true; 91 | default: 92 | return super.onOptionsItemSelected(item); 93 | } 94 | } 95 | 96 | } --------------------------------------------------------------------------------