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

Merge pull request #8 from christocracy/android

Android
parents 34a0297a 5244f8b2
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).
......@@ -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:
......@@ -70,7 +70,10 @@ A full example could be:
// BackgroundGeoLocation is highly configurable.
bgGeo.configure(callbackFn, failureFn, {
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,
stationaryRadius: 20,
distanceFilter: 30,
......@@ -98,7 +101,7 @@ When the plugin detects your user has moved beyond his stationary-region, it eng
## 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
......@@ -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")
## Licence ##
The MIT License
......
......@@ -91,7 +91,10 @@ var app = {
// BackgroundGeoLocation is highly configurable.
bgGeo.configure(callbackFn, failureFn, {
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,
stationaryRadius: 20,
distanceFilter: 30,
......
......@@ -23,8 +23,8 @@ public class BackgroundGpsPlugin extends CordovaPlugin {
private Boolean isEnabled = false;
private String authToken;
private String url;
private String params;
private String stationaryRadius = "30";
private String desiredAccuracy = "100";
private String distanceFilter = "30";
......@@ -38,12 +38,12 @@ public class BackgroundGpsPlugin extends CordovaPlugin {
if (ACTION_START.equalsIgnoreCase(action) && !isEnabled) {
result = true;
if (authToken == null || url == null) {
if (params == null || url == null) {
callbackContext.error("Call configure before calling start");
} else {
callbackContext.success();
updateServiceIntent.putExtra("authToken", authToken);
updateServiceIntent.putExtra("url", url);
updateServiceIntent.putExtra("params", params);
updateServiceIntent.putExtra("stationaryRadius", stationaryRadius);
updateServiceIntent.putExtra("desiredAccuracy", desiredAccuracy);
updateServiceIntent.putExtra("distanceFilter", distanceFilter);
......@@ -62,9 +62,8 @@ public class BackgroundGpsPlugin extends CordovaPlugin {
} else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) {
result = true;
try {
// [authToken, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]);
this.authToken = data.getString(0);
// [params, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]);
this.params = data.getString(0);
this.url = data.getString(1);
this.stationaryRadius = data.getString(2);
this.distanceFilter = data.getString(3);
......
......@@ -6,77 +6,89 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import com.tenforwardconsulting.cordova.bgloc.data.DAOFactory;
import com.tenforwardconsulting.cordova.bgloc.data.LocationDAO;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import static android.telephony.PhoneStateListener.*;
import android.telephony.CellLocation;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.location.Location;
import android.location.Criteria;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Toast;
import static android.telephony.PhoneStateListener.*;
import static java.lang.Math.*;
public class LocationUpdateService extends Service implements LocationListener {
private static final String TAG = "LocationUpdateService";
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_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 SINGLE_LOCATION_UPDATE_ACTION = "com.tenforwardconsulting.cordova.bgloc.SINGLE_LOCATION_UPDATE_ACTION";
private static long STATIONARY_TIMEOUT = 60 * 1000 * 2;
private static final Integer MAX_STATIONARY_ACQUISITION_ATTEMPTS = 3;
private static final String STATIONARY_LOCATION_MONITOR_ACTION = "com.tenforwardconsulting.cordova.bgloc.STATIONARY_LOCATION_MONITOR_ACTION";
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 PowerManager.WakeLock wakeLock;
private Location lastLocation;
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 float stationaryRadius;
private Location stationaryLocation;
private PendingIntent stationaryAlarmPI;
private PendingIntent stationaryLocationPollingPI;
private long stationaryLocationPollingInterval;
private PendingIntent stationaryRegionPI;
private PendingIntent singleUpdatePI;
private Boolean isMoving = false;
private Boolean isAcquiringStationaryLocation = false;
private Boolean isAcquiringSpeed = false;
private Integer stationaryLocationAttempts = 0;
private Integer speedAcquisitionAttempts = 0;
private Integer locationAcquisitionAttempts = 0;
private Integer desiredAccuracy;
private Integer distanceFilter;
private Integer desiredAccuracy = 100;
private Integer distanceFilter = 30;
private Integer scaledDistanceFilter;
private Integer locationTimeout;
private Integer locationTimeout = 30;
private Boolean isDebugging;
private ToneGenerator toneGenerator;
......@@ -86,6 +98,7 @@ public class LocationUpdateService extends Service implements LocationListener {
private LocationManager locationManager;
private AlarmManager alarmManager;
private ConnectivityManager connectivityManager;
private NotificationManager notificationManager;
public static TelephonyManager telephonyManager = null;
@Override
......@@ -104,6 +117,8 @@ public class LocationUpdateService extends Service implements LocationListener {
alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
toneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
notificationManager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// Stop-detection PI
stationaryAlarmPI = PendingIntent.getBroadcast(this, 0, new Intent(STATIONARY_ALARM_ACTION), 0);
......@@ -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);
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)
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));
////
// 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);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wakeLock.acquire();
// Location criteria
......@@ -132,8 +158,13 @@ public class LocationUpdateService extends Service implements LocationListener {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Received start id " + startId + ": " + intent);
if (intent != null) {
authToken = intent.getStringExtra("authToken");
if (intent != null) {
try {
params = new JSONObject(intent.getStringExtra("params"));
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
url = intent.getStringExtra("url");
stationaryRadius = Float.parseFloat(intent.getStringExtra("stationaryRadius"));
distanceFilter = Integer.parseInt(intent.getStringExtra("distanceFilter"));
......@@ -142,26 +173,48 @@ public class LocationUpdateService extends Service implements LocationListener {
locationTimeout = Integer.parseInt(intent.getStringExtra("locationTimeout"));
isDebugging = Boolean.parseBoolean(intent.getStringExtra("isDebugging"));
Log.i(TAG, "- url: " + url);
Log.i(TAG, "- token: " + authToken);
Log.i(TAG, "- stationaryRadius: " + stationaryRadius);
Log.i(TAG, "- distanceFilter: " + distanceFilter);
Log.i(TAG, "- desiredAccuracy: " + desiredAccuracy);
Log.i(TAG, "- locationTimeout: " + locationTimeout);
Log.i(TAG, "- isDebugging: " + isDebugging);
this.setPace(false);
// 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);
}
/**
* 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);
*
*/
Log.i(TAG, "- url: " + url);
Log.i(TAG, "- params: " + params.toString());
Log.i(TAG, "- stationaryRadius: " + stationaryRadius);
Log.i(TAG, "- distanceFilter: " + distanceFilter);
Log.i(TAG, "- desiredAccuracy: " + desiredAccuracy);
Log.i(TAG, "- locationTimeout: " + locationTimeout);
Log.i(TAG, "- isDebugging: " + isDebugging);
this.setPace(false);
//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
......@@ -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.
if (!wasMoving) {
isAcquiringSpeed = true;
speedAcquisitionAttempts = 0;
}
} else {
isAcquiringStationaryLocation = true;
stationaryLocationAttempts = 0;
}
// Temporarily turn on super-aggressive geolocation on all providers when acquiring velocity or stationary location.
if (isAcquiringSpeed || isAcquiringStationaryLocation) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
locationAcquisitionAttempts = 0;
// 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 {
locationManager.requestLocationUpdates(locationManager.getBestProvider(criteria, true), locationTimeout*1000, scaledDistanceFilter, this);
}
......@@ -244,8 +302,11 @@ public class LocationUpdateService extends Service implements LocationListener {
* @param minTime Minimum time required between location updates.
* @return The most accurate and / or timely previously detected location.
*/
public Location getLastBestLocation(int minDistance, long minTime) {
Log.i(TAG, "- fetching last best location");
public Location getLastBestLocation() {
int minDistance = (int) stationaryRadius;
long minTime = System.currentTimeMillis() - (locationTimeout * 1000);
Log.i(TAG, "- fetching last best location " + minDistance + "," + minTime);
Location bestResult = null;
float bestAccuracy = Float.MAX_VALUE;
long bestTime = Long.MIN_VALUE;
......@@ -255,78 +316,86 @@ public class LocationUpdateService extends Service implements LocationListener {
// If no result is found within maxTime, return the newest Location.
List<String> matchingProviders = locationManager.getAllProviders();
for (String provider: matchingProviders) {
Log.d(TAG, "- provider: " + provider);
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
Log.d(TAG, " location: " + location.getLatitude() + "," + location.getLongitude() + "," + location.getAccuracy() + "," + location.getSpeed() + "m/s");
float accuracy = location.getAccuracy();
long time = location.getTime();
Log.d(TAG, "time>minTime: " + (time > minTime) + ", accuracy<bestAccuracy: " + (accuracy < bestAccuracy));
if ((time > minTime && accuracy < bestAccuracy)) {
bestResult = location;
bestAccuracy = accuracy;
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;
}
public void onLocationChanged(Location location) {
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) {
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 (stationaryLocation == null || stationaryLocation.getAccuracy() > location.getAccuracy()) {
stationaryLocation = location;
}
if (stationaryLocationAttempts++ == MAX_STATIONARY_ACQUISITION_ATTEMPTS || (stationaryLocation.getAccuracy() <= stationaryRadius) ) {
if (++locationAcquisitionAttempts == MAX_STATIONARY_ACQUISITION_ATTEMPTS) {
isAcquiringStationaryLocation = false;
startMonitoringStationaryRegion(stationaryLocation);
if (isDebugging) {
startTone("long_beep");
}
} else {
// Unacceptable stationary-location: bail-out and wait for another.
if (isDebugging) {
startTone("beep");
}
return;
}
} else if (isAcquiringSpeed) {
// Make *hoo* sound, acquiring speed.
if (isDebugging) {
toneGenerator.startTone(ToneGenerator.TONE_SUP_RADIO_ACK);
}
if (speedAcquisitionAttempts++ == MAX_SPEED_ACQUISITION_ATTEMPTS) {
if (++locationAcquisitionAttempts == MAX_SPEED_ACQUISITION_ATTEMPTS) {
// Got enough samples, assume we're confident in reported speed now. Play "woohoo" sound.
if (isDebugging) {
// @sound doodly-doo
toneGenerator.startTone(ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE);
startTone("doodly_doo");
}
isAcquiringSpeed = false;
scaledDistanceFilter = calculateDistanceFilter(location.getSpeed());
setPace(true);
} else {
if (isDebugging) {
startTone("beep");
}
return;
}
} else if (isMoving) {
// Only reset stationaryAlarm when speed is detected, prevents spurious locations from resetting when stopped.
if (location.getSpeed() > 0) {
if (isDebugging) {
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();
}
// Calculate latest distanceFilter, if it changed by 5 m/s, we'll reconfigure our pace.
Integer newDistanceFilter = calculateDistanceFilter(location.getSpeed());
if (newDistanceFilter != scaledDistanceFilter) {
if (newDistanceFilter != scaledDistanceFilter.intValue()) {
Log.i(TAG, "- updated distanceFilter, new: " + newDistanceFilter + ", old: " + scaledDistanceFilter);
scaledDistanceFilter = newDistanceFilter;
setPace(true);
}
if (location.distanceTo(lastLocation) < distanceFilter) {
return;
}
} else if (stationaryLocation != null) {
return;
}
// Go ahead and cache, push to server
lastLocation = location;
......@@ -339,7 +408,31 @@ public class LocationUpdateService extends Service implements LocationListener {
Log.d(TAG, "Network unavailable, waiting for now");
}
}
/**
* 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() {
alarmManager.cancel(stationaryAlarmPI);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + STATIONARY_TIMEOUT, stationaryAlarmPI); // Millisec * Second * Minute
......@@ -347,7 +440,7 @@ public class LocationUpdateService extends Service implements LocationListener {
private Integer calculateDistanceFilter(Float speed) {
Double newDistanceFilter = (double) distanceFilter;
if (speed > 3 && speed < 100) {
if (speed < 100) {
float roundedDistanceFilter = (round(speed / 5) * 5);
newDistanceFilter = pow(roundedDistanceFilter, 2) + (double) distanceFilter;
}
......@@ -355,47 +448,65 @@ public class LocationUpdateService extends Service implements LocationListener {
}
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);
stationaryLocation = location;
// Remove previous stationaryRegionPI just in case.
locationManager.removeProximityAlert(stationaryRegionPI);
Log.i(TAG, "- startMonitoringStationaryRegion (" + location.getLatitude() + "," + location.getLongitude() + "), accuracy:" + location.getAccuracy());
// Here be the execution of the stationary region monitor
locationManager.addProximityAlert(
location.getLatitude(),
location.getLongitude(),
(location.getAccuracy() < stationaryRadius) ? stationaryRadius : location.getAccuracy(),
-1,
(long)-1,
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!
*/
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) {
new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100).startTone(ToneGenerator.TONE_CDMA_CONFIRM);
}
// There MUST be a valid, recent location if this event-handler was called.
Location location = getLastBestLocation((int) stationaryRadius, locationTimeout * 1000);
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!");
startTone("beep_beep_beep");
}
// Cancel the periodic stationary location monitor alarm.
alarmManager.cancel(stationaryLocationPollingPI);
// Kill the current region-monitor we just walked out of.
locationManager.removeProximityAlert(stationaryRegionPI);
// Engage aggressive tracking.
this.setPace(true);
}
......@@ -403,27 +514,32 @@ public class LocationUpdateService extends Service implements LocationListener {
/**
* TODO Experimental cell-tower change system; something like ios significant changes.
*/
public void onCellLocationChanged(CellLocation cellLocation) {
Log.i(TAG, "- onCellLocationChanged");
Location location = getLastBestLocation((int) stationaryRadius, locationTimeout * 1000);
if (location != null) {
Log.i(TAG, "location: " + location.getLatitude() + "," + location.getLongitude() + ", accuracy: " + location.getAccuracy());
public void onCellLocationChange(CellLocation cellLocation) {
Log.i(TAG, "- onCellLocationChange" + cellLocation.toString());
if (isDebugging) {
Toast.makeText(this, "Cellular location change", Toast.LENGTH_LONG).show();
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.
* UN-USED
*/
private BroadcastReceiver singleUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
unregisterReceiver(singleUpdateReceiver);
String key = LocationManager.KEY_LOCATION_CHANGED;
Location location = (Location)intent.getExtras().get(key);
if (location != null)
onLocationChanged(location);
locationManager.removeUpdates(singleUpdatePI);
if (location != null) {
Log.d(TAG, "- singleUpdateReciever" + location.toString());
onPollStationaryLocation(location);
}
}
};
......@@ -434,11 +550,29 @@ public class LocationUpdateService extends Service implements LocationListener {
@Override
public void onReceive(Context context, Intent intent)
{
// Put here YOUR code.
Log.i(TAG, "- stationaryAlarm fired");
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
*/
......@@ -457,7 +591,11 @@ public class LocationUpdateService extends Service implements LocationListener {
}
else {
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 {
@Override
public void onCellLocationChanged(CellLocation location)
{
onCellLocationChanged(location);
onCellLocationChange(location);
}
};
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
Log.d(TAG, "- onProviderDisabled: " + provider);
}
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
Log.d(TAG, "- onProviderEnabled: " + provider);
}
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
Log.d(TAG, "- onStatusChanged: " + provider + ", status: " + status);
}
private void schedulePostLocations() {
PostLocationTask task = new LocationUpdateService.PostLocationTask();
Log.d(TAG, "beforeexecute " + task.getStatus());
......@@ -505,8 +643,6 @@ public class LocationUpdateService extends Service implements LocationListener {
Log.i(TAG, "Posting native location update: " + l);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost request = new HttpPost(url);
JSONObject params = new JSONObject();
params.put("auth_token", authToken);
JSONObject location = new JSONObject();
location.put("latitude", l.getLatitude());
......@@ -564,12 +700,23 @@ public class LocationUpdateService extends Service implements LocationListener {
}
private void cleanUp() {
locationManager.removeUpdates(this);
locationManager.removeProximityAlert(stationaryRegionPI);
alarmManager.cancel(stationaryAlarmPI);
alarmManager.cancel(stationaryLocationPollingPI);
toneGenerator.release();
unregisterReceiver(stationaryAlarmReceiver);
unregisterReceiver(stationaryRegionReceiver);
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();
}
......
var exec = require("cordova/exec");
module.exports = {
configure: function(success, failure, config) {
var authToken = config.authToken || 'BackgroundGeoLocation_auth_token',
var params = JSON.stringify(config.params || {}),
url = config.url || 'BackgroundGeoLocation_url',
stationaryRadius = (config.stationaryRadius >= 0) ? config.stationaryRadius : 50, // meters
distanceFilter = (config.distanceFilter >= 0) ? config.distanceFilter : 500, // meters
locationTimeout = (config.locationTimeout >= 0) ? config.locationTimeout : 60, // seconds
locationTimeout = (config.locationTimeout >= 0) ? config.locationTimeout : 60, // seconds
desiredAccuracy = (config.desiredAccuracy >= 0) ? config.desiredAccuracy : 100; // meters
debug = config.debug || false;
......@@ -13,7 +13,7 @@ module.exports = {
failure || function() {},
'BackgroundGeoLocation',
'configure',
[authToken, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]);
[params, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug]);
},
start: function(success, failure, config) {
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