Commit 2d69f9e5 authored by Chris Scott's avatar Chris Scott

Merge pull request #8 from christocracy/android

Android
parents 34a0297a 5244f8b2
BackgroundGeoLocation BackgroundGeoLocation
============================== ==============================
Cross-platform background geolocation for Cordova / PhoneGap. Cross-platform background geolocation for Cordova / PhoneGap with battery-saving "circular region monitoring" and "stop detection".
Follows the [Cordova Plugin spec](https://github.com/apache/cordova-plugman/blob/master/plugin_spec.md), so that it works with [Plugman](https://github.com/apache/cordova-plugman). Follows the [Cordova Plugin spec](https://github.com/apache/cordova-plugman/blob/master/plugin_spec.md), so that it works with [Plugman](https://github.com/apache/cordova-plugman).
...@@ -20,7 +20,7 @@ The plugin creates the object `window.plugins.backgroundGeoLocation` with the me ...@@ -20,7 +20,7 @@ The plugin creates the object `window.plugins.backgroundGeoLocation` with the me
``` ```
phonegap plugin add https://github.com/christocracy/cordova-plugin-background-geolocation.git cordova plugin add https://github.com/christocracy/cordova-plugin-background-geolocation.git
``` ```
A full example could be: A full example could be:
...@@ -70,7 +70,10 @@ A full example could be: ...@@ -70,7 +70,10 @@ A full example could be:
// BackgroundGeoLocation is highly configurable. // BackgroundGeoLocation is highly configurable.
bgGeo.configure(callbackFn, failureFn, { bgGeo.configure(callbackFn, failureFn, {
url: 'http://only.for.android.com/update_location.json', // <-- only required for Android; ios allows javascript callbacks for your http url: 'http://only.for.android.com/update_location.json', // <-- only required for Android; ios allows javascript callbacks for your http
authToken: 'user_secret_auth_token', // <-- only required for Android; ios allows javascript callbacks for your http params: { // HTTP POST params sent to your server when persisting locations.
auth_token: 'user_secret_auth_token'
foo: 'bar'
},
desiredAccuracy: 10, desiredAccuracy: 10,
stationaryRadius: 20, stationaryRadius: 20,
distanceFilter: 30, distanceFilter: 30,
...@@ -98,7 +101,7 @@ When the plugin detects your user has moved beyond his stationary-region, it eng ...@@ -98,7 +101,7 @@ When the plugin detects your user has moved beyond his stationary-region, it eng
## iOS and Android ## iOS and Android
The plugin works best with iOS but Android is currently under heavy development and coming along well with features being ported from iOS. Those are the only two supported platforms but I foresee a Windows Phone version once I get a client who desires that. The plugin works with iOS and Android
### Config ### Config
...@@ -160,7 +163,6 @@ Compare now background-geolocation in the scope of a city. In this image, the l ...@@ -160,7 +163,6 @@ Compare now background-geolocation in the scope of a city. In this image, the l
![distanceFilter at city scale](/distance-filter-city.png "distanceFilter at city scale") ![distanceFilter at city scale](/distance-filter-city.png "distanceFilter at city scale")
## Licence ## ## Licence ##
The MIT License The MIT License
......
...@@ -91,7 +91,10 @@ var app = { ...@@ -91,7 +91,10 @@ var app = {
// BackgroundGeoLocation is highly configurable. // BackgroundGeoLocation is highly configurable.
bgGeo.configure(callbackFn, failureFn, { bgGeo.configure(callbackFn, failureFn, {
url: 'http://only.for.android.com/update_location.json', // <-- only required for Android; ios allows javascript callbacks for your http url: 'http://only.for.android.com/update_location.json', // <-- only required for Android; ios allows javascript callbacks for your http
authToken: 'user_secret_auth_token', // <-- only required for Android; ios allows javascript callbacks for your http params: { // HTTP POST params sent to your server when persisting locations.
auth_token: 'user_secret_auth_token'
foo: 'bar'
},
desiredAccuracy: 10, desiredAccuracy: 10,
stationaryRadius: 20, stationaryRadius: 20,
distanceFilter: 30, distanceFilter: 30,
......
...@@ -23,8 +23,8 @@ public class BackgroundGpsPlugin extends CordovaPlugin { ...@@ -23,8 +23,8 @@ public class BackgroundGpsPlugin extends CordovaPlugin {
private Boolean isEnabled = false; private Boolean isEnabled = false;
private String authToken;
private String url; private String url;
private String params;
private String stationaryRadius = "30"; private String stationaryRadius = "30";
private String desiredAccuracy = "100"; private String desiredAccuracy = "100";
private String distanceFilter = "30"; private String distanceFilter = "30";
...@@ -38,12 +38,12 @@ public class BackgroundGpsPlugin extends CordovaPlugin { ...@@ -38,12 +38,12 @@ public class BackgroundGpsPlugin extends CordovaPlugin {
if (ACTION_START.equalsIgnoreCase(action) && !isEnabled) { if (ACTION_START.equalsIgnoreCase(action) && !isEnabled) {
result = true; result = true;
if (authToken == null || url == null) { if (params == null || url == null) {
callbackContext.error("Call configure before calling start"); callbackContext.error("Call configure before calling start");
} else { } else {
callbackContext.success(); callbackContext.success();
updateServiceIntent.putExtra("authToken", authToken);
updateServiceIntent.putExtra("url", url); updateServiceIntent.putExtra("url", url);
updateServiceIntent.putExtra("params", params);
updateServiceIntent.putExtra("stationaryRadius", stationaryRadius); updateServiceIntent.putExtra("stationaryRadius", stationaryRadius);
updateServiceIntent.putExtra("desiredAccuracy", desiredAccuracy); updateServiceIntent.putExtra("desiredAccuracy", desiredAccuracy);
updateServiceIntent.putExtra("distanceFilter", distanceFilter); updateServiceIntent.putExtra("distanceFilter", distanceFilter);
...@@ -62,9 +62,8 @@ public class BackgroundGpsPlugin extends CordovaPlugin { ...@@ -62,9 +62,8 @@ public class BackgroundGpsPlugin extends CordovaPlugin {
} else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) { } else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) {
result = true; result = true;
try { try {
// [authToken, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]); // [params, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]);
this.params = data.getString(0);
this.authToken = data.getString(0);
this.url = data.getString(1); this.url = data.getString(1);
this.stationaryRadius = data.getString(2); this.stationaryRadius = data.getString(2);
this.distanceFilter = data.getString(3); this.distanceFilter = data.getString(3);
......
...@@ -6,43 +6,51 @@ import org.apache.http.HttpResponse; ...@@ -6,43 +6,51 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import com.tenforwardconsulting.cordova.bgloc.data.DAOFactory; import com.tenforwardconsulting.cordova.bgloc.data.DAOFactory;
import com.tenforwardconsulting.cordova.bgloc.data.LocationDAO; import com.tenforwardconsulting.cordova.bgloc.data.LocationDAO;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.ToneGenerator; import android.media.ToneGenerator;
import android.telephony.PhoneStateListener; import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import static android.telephony.PhoneStateListener.*;
import android.telephony.CellLocation; import android.telephony.CellLocation;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.location.Location; import android.location.Location;
import android.location.Criteria; import android.location.Criteria;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import static android.telephony.PhoneStateListener.*;
import static java.lang.Math.*; import static java.lang.Math.*;
public class LocationUpdateService extends Service implements LocationListener { public class LocationUpdateService extends Service implements LocationListener {
...@@ -50,33 +58,37 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -50,33 +58,37 @@ public class LocationUpdateService extends Service implements LocationListener {
private static final String STATIONARY_REGION_ACTION = "com.tenforwardconsulting.cordova.bgloc.STATIONARY_REGION_ACTION"; private static final String STATIONARY_REGION_ACTION = "com.tenforwardconsulting.cordova.bgloc.STATIONARY_REGION_ACTION";
private static final String STATIONARY_ALARM_ACTION = "com.tenforwardconsulting.cordova.bgloc.STATIONARY_ALARM_ACTION"; private static final String STATIONARY_ALARM_ACTION = "com.tenforwardconsulting.cordova.bgloc.STATIONARY_ALARM_ACTION";
private static final String SINGLE_LOCATION_UPDATE_ACTION = "com.tenforwardconsulting.cordova.bgloc.SINGLE_LOCATION_UPDATE_ACTION"; private static final String SINGLE_LOCATION_UPDATE_ACTION = "com.tenforwardconsulting.cordova.bgloc.SINGLE_LOCATION_UPDATE_ACTION";
private static long STATIONARY_TIMEOUT = 60 * 1000 * 2; private static final String STATIONARY_LOCATION_MONITOR_ACTION = "com.tenforwardconsulting.cordova.bgloc.STATIONARY_LOCATION_MONITOR_ACTION";
private static final Integer MAX_STATIONARY_ACQUISITION_ATTEMPTS = 3; private static final long STATIONARY_TIMEOUT = 5 * 1000 * 60; // 5 minutes.
private static final long STATIONARY_LOCATION_POLLING_INTERVAL_LAZY = 3 * 1000 * 60; // 3 minutes.
private static final long STATIONARY_LOCATION_POLLING_INTERVAL_AGGRESSIVE = 1 * 1000 * 60; // 1 minute.
private static final Integer MAX_STATIONARY_ACQUISITION_ATTEMPTS = 5;
private static final Integer MAX_SPEED_ACQUISITION_ATTEMPTS = 3; private static final Integer MAX_SPEED_ACQUISITION_ATTEMPTS = 3;
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
private Location lastLocation; private Location lastLocation;
private long lastUpdateTime = 0l; private long lastUpdateTime = 0l;
private String authToken = "HypVBMmDxbh76pHpwots"; private JSONObject params;
private String url = "http://192.168.2.15:3000/users/current_location.json"; private String url = "http://192.168.2.15:3000/users/current_location.json";
private float stationaryRadius; private float stationaryRadius;
private Location stationaryLocation; private Location stationaryLocation;
private PendingIntent stationaryAlarmPI; private PendingIntent stationaryAlarmPI;
private PendingIntent stationaryLocationPollingPI;
private long stationaryLocationPollingInterval;
private PendingIntent stationaryRegionPI; private PendingIntent stationaryRegionPI;
private PendingIntent singleUpdatePI; private PendingIntent singleUpdatePI;
private Boolean isMoving = false; private Boolean isMoving = false;
private Boolean isAcquiringStationaryLocation = false; private Boolean isAcquiringStationaryLocation = false;
private Boolean isAcquiringSpeed = false; private Boolean isAcquiringSpeed = false;
private Integer stationaryLocationAttempts = 0; private Integer locationAcquisitionAttempts = 0;
private Integer speedAcquisitionAttempts = 0;
private Integer desiredAccuracy; private Integer desiredAccuracy = 100;
private Integer distanceFilter; private Integer distanceFilter = 30;
private Integer scaledDistanceFilter; private Integer scaledDistanceFilter;
private Integer locationTimeout; private Integer locationTimeout = 30;
private Boolean isDebugging; private Boolean isDebugging;
private ToneGenerator toneGenerator; private ToneGenerator toneGenerator;
...@@ -86,6 +98,7 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -86,6 +98,7 @@ public class LocationUpdateService extends Service implements LocationListener {
private LocationManager locationManager; private LocationManager locationManager;
private AlarmManager alarmManager; private AlarmManager alarmManager;
private ConnectivityManager connectivityManager; private ConnectivityManager connectivityManager;
private NotificationManager notificationManager;
public static TelephonyManager telephonyManager = null; public static TelephonyManager telephonyManager = null;
@Override @Override
...@@ -104,6 +117,8 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -104,6 +117,8 @@ public class LocationUpdateService extends Service implements LocationListener {
alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
toneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100); toneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
notificationManager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// Stop-detection PI // Stop-detection PI
stationaryAlarmPI = PendingIntent.getBroadcast(this, 0, new Intent(STATIONARY_ALARM_ACTION), 0); stationaryAlarmPI = PendingIntent.getBroadcast(this, 0, new Intent(STATIONARY_ALARM_ACTION), 0);
...@@ -113,12 +128,23 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -113,12 +128,23 @@ public class LocationUpdateService extends Service implements LocationListener {
stationaryRegionPI = PendingIntent.getBroadcast(this, 0, new Intent(STATIONARY_REGION_ACTION), PendingIntent.FLAG_CANCEL_CURRENT); stationaryRegionPI = PendingIntent.getBroadcast(this, 0, new Intent(STATIONARY_REGION_ACTION), PendingIntent.FLAG_CANCEL_CURRENT);
registerReceiver(stationaryRegionReceiver, new IntentFilter(STATIONARY_REGION_ACTION)); registerReceiver(stationaryRegionReceiver, new IntentFilter(STATIONARY_REGION_ACTION));
// Stationary location monitor PI
stationaryLocationPollingPI = PendingIntent.getBroadcast(this, 0, new Intent(STATIONARY_LOCATION_MONITOR_ACTION), 0);
registerReceiver(stationaryLocationMonitorReceiver, new IntentFilter(STATIONARY_LOCATION_MONITOR_ACTION));
// One-shot PI (TODO currently unused) // One-shot PI (TODO currently unused)
singleUpdatePI = PendingIntent.getBroadcast(this, 0, new Intent(SINGLE_LOCATION_UPDATE_ACTION), PendingIntent.FLAG_UPDATE_CURRENT); singleUpdatePI = PendingIntent.getBroadcast(this, 0, new Intent(SINGLE_LOCATION_UPDATE_ACTION), PendingIntent.FLAG_CANCEL_CURRENT);
registerReceiver(singleUpdateReceiver, new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION)); registerReceiver(singleUpdateReceiver, new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION));
////
// DISABLED
// Listen to Cell-tower switches (NOTE does not operate while suspended)
//telephonyManager.listen(phoneStateListener, LISTEN_CELL_LOCATION);
//
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wakeLock.acquire(); wakeLock.acquire();
// Location criteria // Location criteria
...@@ -133,7 +159,12 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -133,7 +159,12 @@ public class LocationUpdateService extends Service implements LocationListener {
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Received start id " + startId + ": " + intent); Log.i(TAG, "Received start id " + startId + ": " + intent);
if (intent != null) { if (intent != null) {
authToken = intent.getStringExtra("authToken"); try {
params = new JSONObject(intent.getStringExtra("params"));
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
url = intent.getStringExtra("url"); url = intent.getStringExtra("url");
stationaryRadius = Float.parseFloat(intent.getStringExtra("stationaryRadius")); stationaryRadius = Float.parseFloat(intent.getStringExtra("stationaryRadius"));
distanceFilter = Integer.parseInt(intent.getStringExtra("distanceFilter")); distanceFilter = Integer.parseInt(intent.getStringExtra("distanceFilter"));
...@@ -142,8 +173,27 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -142,8 +173,27 @@ public class LocationUpdateService extends Service implements LocationListener {
locationTimeout = Integer.parseInt(intent.getStringExtra("locationTimeout")); locationTimeout = Integer.parseInt(intent.getStringExtra("locationTimeout"));
isDebugging = Boolean.parseBoolean(intent.getStringExtra("isDebugging")); isDebugging = Boolean.parseBoolean(intent.getStringExtra("isDebugging"));
// Build a Notification required for running service in foreground.
Intent main = new Intent(this, BackgroundGpsPlugin.class);
main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle("Background tracking");
builder.setContentText("ENABLED");
builder.setSmallIcon(android.R.drawable.ic_menu_mylocation);
builder.setContentIntent(pendingIntent);
Notification notification;
if (android.os.Build.VERSION.SDK_INT >= 16) {
notification = buildForegroundNotification(builder);
} else {
notification = buildForegroundNotificationCompat(builder);
}
notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_NO_CLEAR;
startForeground(startId, notification);
}
Log.i(TAG, "- url: " + url); Log.i(TAG, "- url: " + url);
Log.i(TAG, "- token: " + authToken); Log.i(TAG, "- params: " + params.toString());
Log.i(TAG, "- stationaryRadius: " + stationaryRadius); Log.i(TAG, "- stationaryRadius: " + stationaryRadius);
Log.i(TAG, "- distanceFilter: " + distanceFilter); Log.i(TAG, "- distanceFilter: " + distanceFilter);
Log.i(TAG, "- desiredAccuracy: " + desiredAccuracy); Log.i(TAG, "- desiredAccuracy: " + desiredAccuracy);
...@@ -151,17 +201,20 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -151,17 +201,20 @@ public class LocationUpdateService extends Service implements LocationListener {
Log.i(TAG, "- isDebugging: " + isDebugging); Log.i(TAG, "- isDebugging: " + isDebugging);
this.setPace(false); this.setPace(false);
}
/**
* Experimental cell-location-change handler. Hoping to implement something like ios Significant Changes system
*
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, LISTEN_CELL_LOCATION);
*
*/
//We want this service to continue running until it is explicitly stopped //We want this service to continue running until it is explicitly stopped
return START_STICKY; return START_REDELIVER_INTENT;
}
@TargetApi(16)
private Notification buildForegroundNotification(Notification.Builder builder) {
return builder.build();
}
@SuppressWarnings("deprecation")
@TargetApi(15)
private Notification buildForegroundNotificationCompat(Notification.Builder builder) {
return builder.getNotification();
} }
@Override @Override
...@@ -195,16 +248,21 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -195,16 +248,21 @@ public class LocationUpdateService extends Service implements LocationListener {
// setPace can be called while moving, after distanceFilter has been recalculated. We don't want to re-acquire velocity in this case. // setPace can be called while moving, after distanceFilter has been recalculated. We don't want to re-acquire velocity in this case.
if (!wasMoving) { if (!wasMoving) {
isAcquiringSpeed = true; isAcquiringSpeed = true;
speedAcquisitionAttempts = 0;
} }
} else { } else {
isAcquiringStationaryLocation = true; isAcquiringStationaryLocation = true;
stationaryLocationAttempts = 0;
} }
// Temporarily turn on super-aggressive geolocation on all providers when acquiring velocity or stationary location. // Temporarily turn on super-aggressive geolocation on all providers when acquiring velocity or stationary location.
if (isAcquiringSpeed || isAcquiringStationaryLocation) { if (isAcquiringSpeed || isAcquiringStationaryLocation) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); locationAcquisitionAttempts = 0;
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this); // Turn on each provider aggressively for a short period of time
List<String> matchingProviders = locationManager.getAllProviders();
for (String provider: matchingProviders) {
if (provider != LocationManager.PASSIVE_PROVIDER) {
locationManager.requestLocationUpdates(provider, 0, 0, this);
}
}
} else { } else {
locationManager.requestLocationUpdates(locationManager.getBestProvider(criteria, true), locationTimeout*1000, scaledDistanceFilter, this); locationManager.requestLocationUpdates(locationManager.getBestProvider(criteria, true), locationTimeout*1000, scaledDistanceFilter, this);
} }
...@@ -244,8 +302,11 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -244,8 +302,11 @@ public class LocationUpdateService extends Service implements LocationListener {
* @param minTime Minimum time required between location updates. * @param minTime Minimum time required between location updates.
* @return The most accurate and / or timely previously detected location. * @return The most accurate and / or timely previously detected location.
*/ */
public Location getLastBestLocation(int minDistance, long minTime) { public Location getLastBestLocation() {
Log.i(TAG, "- fetching last best location"); int minDistance = (int) stationaryRadius;
long minTime = System.currentTimeMillis() - (locationTimeout * 1000);
Log.i(TAG, "- fetching last best location " + minDistance + "," + minTime);
Location bestResult = null; Location bestResult = null;
float bestAccuracy = Float.MAX_VALUE; float bestAccuracy = Float.MAX_VALUE;
long bestTime = Long.MIN_VALUE; long bestTime = Long.MIN_VALUE;
...@@ -255,78 +316,86 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -255,78 +316,86 @@ public class LocationUpdateService extends Service implements LocationListener {
// If no result is found within maxTime, return the newest Location. // If no result is found within maxTime, return the newest Location.
List<String> matchingProviders = locationManager.getAllProviders(); List<String> matchingProviders = locationManager.getAllProviders();
for (String provider: matchingProviders) { for (String provider: matchingProviders) {
Log.d(TAG, "- provider: " + provider);
Location location = locationManager.getLastKnownLocation(provider); Location location = locationManager.getLastKnownLocation(provider);
if (location != null) { if (location != null) {
Log.d(TAG, " location: " + location.getLatitude() + "," + location.getLongitude() + "," + location.getAccuracy() + "," + location.getSpeed() + "m/s");
float accuracy = location.getAccuracy(); float accuracy = location.getAccuracy();
long time = location.getTime(); long time = location.getTime();
Log.d(TAG, "time>minTime: " + (time > minTime) + ", accuracy<bestAccuracy: " + (accuracy < bestAccuracy));
if ((time > minTime && accuracy < bestAccuracy)) { if ((time > minTime && accuracy < bestAccuracy)) {
bestResult = location; bestResult = location;
bestAccuracy = accuracy; bestAccuracy = accuracy;
bestTime = time; bestTime = time;
} }
else if (time < minTime && bestAccuracy == Float.MAX_VALUE && time > bestTime) {
bestResult = location;
bestTime = time;
}
} }
} }
// If the best result is beyond the allowed time limit, or the accuracy of the
// best result is wider than the acceptable maximum distance, request a single update.
// This check simply implements the same conditions we set when requesting regular
// location updates every [minTime] and [minDistance].
if ((bestTime < minTime || bestAccuracy > minDistance)) {
bestResult = null;
}
return bestResult; return bestResult;
} }
public void onLocationChanged(Location location) { public void onLocationChanged(Location location) {
Log.d(TAG, "- onLocationChanged: " + location.getLatitude() + "," + location.getLongitude() + ", accuracy: " + location.getAccuracy() + ", isMoving: " + isMoving + ", speed: " + location.getSpeed()); Log.d(TAG, "- onLocationChanged: " + location.getLatitude() + "," + location.getLongitude() + ", accuracy: " + location.getAccuracy() + ", isMoving: " + isMoving + ", speed: " + location.getSpeed());
if (!isMoving && !isAcquiringStationaryLocation && stationaryLocation==null) {
// Perhaps our GPS signal was interupted, re-acquire a stationaryLocation now.
setPace(false);
}
if (isDebugging) { if (isDebugging) {
Toast.makeText(this, "mv:"+isMoving+",acy:"+location.getAccuracy()+",v:"+location.getSpeed()+",df:"+scaledDistanceFilter, Toast.LENGTH_LONG).show(); Toast.makeText(this, "mv:"+isMoving+",acy:"+location.getAccuracy()+",v:"+location.getSpeed()+",df:"+scaledDistanceFilter, Toast.LENGTH_LONG).show();
toneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP);
} }
if (isAcquiringStationaryLocation) { if (isAcquiringStationaryLocation) {
if (stationaryLocation == null || stationaryLocation.getAccuracy() > location.getAccuracy()) { if (stationaryLocation == null || stationaryLocation.getAccuracy() > location.getAccuracy()) {
stationaryLocation = location; stationaryLocation = location;
} }
if (stationaryLocationAttempts++ == MAX_STATIONARY_ACQUISITION_ATTEMPTS || (stationaryLocation.getAccuracy() <= stationaryRadius) ) { if (++locationAcquisitionAttempts == MAX_STATIONARY_ACQUISITION_ATTEMPTS) {
isAcquiringStationaryLocation = false; isAcquiringStationaryLocation = false;
startMonitoringStationaryRegion(stationaryLocation); startMonitoringStationaryRegion(stationaryLocation);
if (isDebugging) {
startTone("long_beep");
}
} else { } else {
// Unacceptable stationary-location: bail-out and wait for another. // Unacceptable stationary-location: bail-out and wait for another.
if (isDebugging) {
startTone("beep");
}
return; return;
} }
} else if (isAcquiringSpeed) { } else if (isAcquiringSpeed) {
// Make *hoo* sound, acquiring speed. if (++locationAcquisitionAttempts == MAX_SPEED_ACQUISITION_ATTEMPTS) {
if (isDebugging) {
toneGenerator.startTone(ToneGenerator.TONE_SUP_RADIO_ACK);
}
if (speedAcquisitionAttempts++ == MAX_SPEED_ACQUISITION_ATTEMPTS) {
// Got enough samples, assume we're confident in reported speed now. Play "woohoo" sound. // Got enough samples, assume we're confident in reported speed now. Play "woohoo" sound.
if (isDebugging) { if (isDebugging) {
// @sound doodly-doo startTone("doodly_doo");
toneGenerator.startTone(ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE);
} }
isAcquiringSpeed = false; isAcquiringSpeed = false;
scaledDistanceFilter = calculateDistanceFilter(location.getSpeed()); scaledDistanceFilter = calculateDistanceFilter(location.getSpeed());
setPace(true); setPace(true);
} else { } else {
if (isDebugging) {
startTone("beep");
}
return; return;
} }
} else if (isMoving) { } else if (isMoving) {
// Only reset stationaryAlarm when speed is detected, prevents spurious locations from resetting when stopped. if (isDebugging) {
if (location.getSpeed() > 0) { startTone("beep");
}
// Only reset stationaryAlarm when accurate speed is detected, prevents spurious locations from resetting when stopped.
if ( (location.getSpeed() >= 1) && (location.getAccuracy() <= stationaryRadius) ) {
resetStationaryAlarm(); resetStationaryAlarm();
} }
// Calculate latest distanceFilter, if it changed by 5 m/s, we'll reconfigure our pace. // Calculate latest distanceFilter, if it changed by 5 m/s, we'll reconfigure our pace.
Integer newDistanceFilter = calculateDistanceFilter(location.getSpeed()); Integer newDistanceFilter = calculateDistanceFilter(location.getSpeed());
if (newDistanceFilter != scaledDistanceFilter) { if (newDistanceFilter != scaledDistanceFilter.intValue()) {
Log.i(TAG, "- updated distanceFilter, new: " + newDistanceFilter + ", old: " + scaledDistanceFilter); Log.i(TAG, "- updated distanceFilter, new: " + newDistanceFilter + ", old: " + scaledDistanceFilter);
scaledDistanceFilter = newDistanceFilter; scaledDistanceFilter = newDistanceFilter;
setPace(true); setPace(true);
} }
if (location.distanceTo(lastLocation) < distanceFilter) {
return;
}
} else if (stationaryLocation != null) {
return;
} }
// Go ahead and cache, push to server // Go ahead and cache, push to server
lastLocation = location; lastLocation = location;
...@@ -340,6 +409,30 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -340,6 +409,30 @@ public class LocationUpdateService extends Service implements LocationListener {
} }
} }
/**
* Plays debug sound
* @param name
*/
private void startTone(String name) {
int tone = 0;
int duration = 1000;
if (name.equals("beep")) {
tone = ToneGenerator.TONE_PROP_BEEP;
} else if (name.equals("beep_beep_beep")) {
tone = ToneGenerator.TONE_CDMA_CONFIRM;
} else if (name.equals("long_beep")) {
tone = ToneGenerator.TONE_CDMA_ABBR_ALERT;
} else if (name.equals("doodly_doo")) {
tone = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE;
} else if (name.equals("chirp_chirp_chirp")) {
tone = ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD;
} else if (name.equals("dialtone")) {
tone = ToneGenerator.TONE_SUP_RINGTONE;
}
toneGenerator.startTone(tone, duration);
}
public void resetStationaryAlarm() { public void resetStationaryAlarm() {
alarmManager.cancel(stationaryAlarmPI); alarmManager.cancel(stationaryAlarmPI);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + STATIONARY_TIMEOUT, stationaryAlarmPI); // Millisec * Second * Minute alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + STATIONARY_TIMEOUT, stationaryAlarmPI); // Millisec * Second * Minute
...@@ -347,7 +440,7 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -347,7 +440,7 @@ public class LocationUpdateService extends Service implements LocationListener {
private Integer calculateDistanceFilter(Float speed) { private Integer calculateDistanceFilter(Float speed) {
Double newDistanceFilter = (double) distanceFilter; Double newDistanceFilter = (double) distanceFilter;
if (speed > 3 && speed < 100) { if (speed < 100) {
float roundedDistanceFilter = (round(speed / 5) * 5); float roundedDistanceFilter = (round(speed / 5) * 5);
newDistanceFilter = pow(roundedDistanceFilter, 2) + (double) distanceFilter; newDistanceFilter = pow(roundedDistanceFilter, 2) + (double) distanceFilter;
} }
...@@ -355,47 +448,65 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -355,47 +448,65 @@ public class LocationUpdateService extends Service implements LocationListener {
} }
private void startMonitoringStationaryRegion(Location location) { private void startMonitoringStationaryRegion(Location location) {
Log.i(TAG, "- startMonitoringStationaryRegion (" + location.getLatitude() + "," + location.getLongitude() + "), accuracy:" + location.getAccuracy());
if (isDebugging) {
// @sound "beeeeeeeeeeeeeeeeep"
toneGenerator.startTone(ToneGenerator.TONE_CDMA_ABBR_ALERT);
}
stationaryLocation = location;
locationManager.removeUpdates(this); locationManager.removeUpdates(this);
stationaryLocation = location;
// Remove previous stationaryRegionPI just in case. Log.i(TAG, "- startMonitoringStationaryRegion (" + location.getLatitude() + "," + location.getLongitude() + "), accuracy:" + location.getAccuracy());
locationManager.removeProximityAlert(stationaryRegionPI);
// Here be the execution of the stationary region monitor // Here be the execution of the stationary region monitor
locationManager.addProximityAlert( locationManager.addProximityAlert(
location.getLatitude(), location.getLatitude(),
location.getLongitude(), location.getLongitude(),
(location.getAccuracy() < stationaryRadius) ? stationaryRadius : location.getAccuracy(), (location.getAccuracy() < stationaryRadius) ? stationaryRadius : location.getAccuracy(),
-1, (long)-1,
stationaryRegionPI stationaryRegionPI
); );
startPollingStationaryLocation(STATIONARY_LOCATION_POLLING_INTERVAL_LAZY);
} }
public void startPollingStationaryLocation(long interval) {
// proximity-alerts don't seem to work while suspended in latest Android 4.42 (works in 4.03). Have to use AlarmManager to sample
// location at regular intervals with a one-shot.
stationaryLocationPollingInterval = interval;
alarmManager.cancel(stationaryLocationPollingPI);
long start = System.currentTimeMillis() + (60 * 1000);
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, start, interval, stationaryLocationPollingPI);
}
public void onPollStationaryLocation(Location location) {
if (isMoving) {
return;
}
startTone("beep");
float distance = location.distanceTo(stationaryLocation) - stationaryLocation.getAccuracy() - location.getAccuracy();
Toast.makeText(this, "Stationary exit in " + (stationaryRadius-distance) + "m", Toast.LENGTH_LONG).show();
// TODO http://www.cse.buffalo.edu/~demirbas/publications/proximity.pdf
// determine if we're almost out of stationary-distance and increase monitoring-rate.
Log.i(TAG, "- distance from stationary location: " + distance);
if (distance > stationaryRadius) {
onExitStationaryRegion(location);
} else if (distance > 0) {
startPollingStationaryLocation(STATIONARY_LOCATION_POLLING_INTERVAL_AGGRESSIVE);
} else if (stationaryLocationPollingInterval != STATIONARY_LOCATION_POLLING_INTERVAL_LAZY) {
startPollingStationaryLocation(STATIONARY_LOCATION_POLLING_INTERVAL_LAZY);
}
}
/** /**
* User has exit his stationary region! Initiate aggressive geolocation! * User has exit his stationary region! Initiate aggressive geolocation!
*/ */
public void onExitStationaryRegion() { public void onExitStationaryRegion(Location location) {
// Filter-out spurious region-exits: must have at least a little speed to move out of stationary-region
if (isDebugging) { if (isDebugging) {
new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100).startTone(ToneGenerator.TONE_CDMA_CONFIRM); startTone("beep_beep_beep");
} }
// There MUST be a valid, recent location if this event-handler was called. // Cancel the periodic stationary location monitor alarm.
Location location = getLastBestLocation((int) stationaryRadius, locationTimeout * 1000); alarmManager.cancel(stationaryLocationPollingPI);
if (location != null) {
// Filter-out spurious region-exits: must have at least a little speed to move out of stationary-region.
if (location.getSpeed() < 0.75) {
return;
}
} else {
Log.i(TAG, "- exit stationary region receiver was triggered but could not fetch the last-best location!");
}
// Kill the current region-monitor we just walked out of. // Kill the current region-monitor we just walked out of.
locationManager.removeProximityAlert(stationaryRegionPI); locationManager.removeProximityAlert(stationaryRegionPI);
// Engage aggressive tracking. // Engage aggressive tracking.
this.setPace(true); this.setPace(true);
} }
...@@ -403,27 +514,32 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -403,27 +514,32 @@ public class LocationUpdateService extends Service implements LocationListener {
/** /**
* TODO Experimental cell-tower change system; something like ios significant changes. * TODO Experimental cell-tower change system; something like ios significant changes.
*/ */
public void onCellLocationChanged(CellLocation cellLocation) { public void onCellLocationChange(CellLocation cellLocation) {
Log.i(TAG, "- onCellLocationChanged"); Log.i(TAG, "- onCellLocationChange" + cellLocation.toString());
Location location = getLastBestLocation((int) stationaryRadius, locationTimeout * 1000); if (isDebugging) {
if (location != null) { Toast.makeText(this, "Cellular location change", Toast.LENGTH_LONG).show();
Log.i(TAG, "location: " + location.getLatitude() + "," + location.getLongitude() + ", accuracy: " + location.getAccuracy()); startTone("chirp_chirp_chirp");
}
if (!isMoving && stationaryLocation != null) {
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
criteria.setPowerRequirement(Criteria.POWER_HIGH);
locationManager.requestSingleUpdate(criteria, singleUpdatePI);
} }
} }
/** /**
* Broadcast receiver for receiving a single-update from LocationManager. * Broadcast receiver for receiving a single-update from LocationManager.
* UN-USED
*/ */
private BroadcastReceiver singleUpdateReceiver = new BroadcastReceiver() { private BroadcastReceiver singleUpdateReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
unregisterReceiver(singleUpdateReceiver);
String key = LocationManager.KEY_LOCATION_CHANGED; String key = LocationManager.KEY_LOCATION_CHANGED;
Location location = (Location)intent.getExtras().get(key); Location location = (Location)intent.getExtras().get(key);
if (location != null) if (location != null) {
onLocationChanged(location); Log.d(TAG, "- singleUpdateReciever" + location.toString());
locationManager.removeUpdates(singleUpdatePI); onPollStationaryLocation(location);
}
} }
}; };
...@@ -434,11 +550,29 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -434,11 +550,29 @@ public class LocationUpdateService extends Service implements LocationListener {
@Override @Override
public void onReceive(Context context, Intent intent) public void onReceive(Context context, Intent intent)
{ {
// Put here YOUR code.
Log.i(TAG, "- stationaryAlarm fired"); Log.i(TAG, "- stationaryAlarm fired");
setPace(false); setPace(false);
} }
}; };
/**
* Broadcast receiver to handle stationaryMonitor alarm, fired at low frequency while monitoring stationary-region.
* This is required because latest Android proximity-alerts don't seem to operate while suspended. Regularly polling
* the location seems to trigger the proximity-alerts while suspended.
*/
private BroadcastReceiver stationaryLocationMonitorReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent)
{
Log.i(TAG, "- stationaryLocationMonitorReceiver fired");
if (isDebugging) {
startTone("dialtone");
}
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
criteria.setPowerRequirement(Criteria.POWER_HIGH);
locationManager.requestSingleUpdate(criteria, singleUpdatePI);
}
};
/** /**
* Broadcast receiver which detects a user has exit his circular stationary-region determined by the greater of stationaryLocation.getAccuracy() OR stationaryRadius * Broadcast receiver which detects a user has exit his circular stationary-region determined by the greater of stationaryLocation.getAccuracy() OR stationaryRadius
*/ */
...@@ -457,7 +591,11 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -457,7 +591,11 @@ public class LocationUpdateService extends Service implements LocationListener {
} }
else { else {
Log.d(TAG, "- EXIT"); Log.d(TAG, "- EXIT");
onExitStationaryRegion(); // There MUST be a valid, recent location if this event-handler was called.
Location location = getLastBestLocation();
if (location != null) {
onExitStationaryRegion(location);
}
} }
} }
}; };
...@@ -468,22 +606,22 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -468,22 +606,22 @@ public class LocationUpdateService extends Service implements LocationListener {
@Override @Override
public void onCellLocationChanged(CellLocation location) public void onCellLocationChanged(CellLocation location)
{ {
onCellLocationChanged(location); onCellLocationChange(location);
} }
}; };
public void onProviderDisabled(String provider) { public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
Log.d(TAG, "- onProviderDisabled: " + provider);
} }
public void onProviderEnabled(String provider) { public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
Log.d(TAG, "- onProviderEnabled: " + provider);
} }
public void onStatusChanged(String provider, int status, Bundle extras) { public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
Log.d(TAG, "- onStatusChanged: " + provider + ", status: " + status);
} }
private void schedulePostLocations() { private void schedulePostLocations() {
PostLocationTask task = new LocationUpdateService.PostLocationTask(); PostLocationTask task = new LocationUpdateService.PostLocationTask();
Log.d(TAG, "beforeexecute " + task.getStatus()); Log.d(TAG, "beforeexecute " + task.getStatus());
...@@ -505,8 +643,6 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -505,8 +643,6 @@ public class LocationUpdateService extends Service implements LocationListener {
Log.i(TAG, "Posting native location update: " + l); Log.i(TAG, "Posting native location update: " + l);
DefaultHttpClient httpClient = new DefaultHttpClient(); DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost request = new HttpPost(url); HttpPost request = new HttpPost(url);
JSONObject params = new JSONObject();
params.put("auth_token", authToken);
JSONObject location = new JSONObject(); JSONObject location = new JSONObject();
location.put("latitude", l.getLatitude()); location.put("latitude", l.getLatitude());
...@@ -564,12 +700,23 @@ public class LocationUpdateService extends Service implements LocationListener { ...@@ -564,12 +700,23 @@ public class LocationUpdateService extends Service implements LocationListener {
} }
private void cleanUp() { private void cleanUp() {
locationManager.removeUpdates(this); locationManager.removeUpdates(this);
locationManager.removeProximityAlert(stationaryRegionPI);
alarmManager.cancel(stationaryAlarmPI); alarmManager.cancel(stationaryAlarmPI);
alarmManager.cancel(stationaryLocationPollingPI);
toneGenerator.release();
unregisterReceiver(stationaryAlarmReceiver); unregisterReceiver(stationaryAlarmReceiver);
unregisterReceiver(stationaryRegionReceiver);
unregisterReceiver(singleUpdateReceiver); unregisterReceiver(singleUpdateReceiver);
unregisterReceiver(stationaryRegionReceiver);
unregisterReceiver(stationaryLocationMonitorReceiver);
if (stationaryLocation != null && !isMoving) {
try {
locationManager.removeProximityAlert(stationaryRegionPI);
} catch (Throwable e) {
Log.w(TAG, "- Something bad happened while removing proximity-alert");
}
}
stopForeground(true);
wakeLock.release(); wakeLock.release();
} }
......
var exec = require("cordova/exec"); var exec = require("cordova/exec");
module.exports = { module.exports = {
configure: function(success, failure, config) { configure: function(success, failure, config) {
var authToken = config.authToken || 'BackgroundGeoLocation_auth_token', var params = JSON.stringify(config.params || {}),
url = config.url || 'BackgroundGeoLocation_url', url = config.url || 'BackgroundGeoLocation_url',
stationaryRadius = (config.stationaryRadius >= 0) ? config.stationaryRadius : 50, // meters stationaryRadius = (config.stationaryRadius >= 0) ? config.stationaryRadius : 50, // meters
distanceFilter = (config.distanceFilter >= 0) ? config.distanceFilter : 500, // meters distanceFilter = (config.distanceFilter >= 0) ? config.distanceFilter : 500, // meters
...@@ -13,7 +13,7 @@ module.exports = { ...@@ -13,7 +13,7 @@ module.exports = {
failure || function() {}, failure || function() {},
'BackgroundGeoLocation', 'BackgroundGeoLocation',
'configure', 'configure',
[authToken, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]); [params, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]);
}, },
start: function(success, failure, config) { start: function(success, failure, config) {
exec(success || function() {}, exec(success || function() {},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment