Commit 5ba2132f authored by Chris Scott's avatar Chris Scott

Implement stationary-heartbeat location monitor in order to keep...

Implement stationary-heartbeat location monitor in order to keep proximity-detection alive.  Android 4.4 proximity detectors are supposed to fire after 4min while suspended but I don'tt see that happening.  This Alarm of fixed time to ping the GPS at regular interval seems to make stationary-exit work well.  Also turned on CellLocationChange event-listener but that only works in foreground unfortunately.  IF that actually worked, we could have something similar to ios Significant Changes system.  This is why we have to poll the GPS regularly while in stationary-mode
parent 1d72a53d
......@@ -11,6 +11,7 @@ import org.json.JSONObject;
import com.tenforwardconsulting.cordova.bgloc.data.DAOFactory;
import com.tenforwardconsulting.cordova.bgloc.data.LocationDAO;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.media.AudioManager;
......@@ -19,6 +20,8 @@ import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.telephony.CellLocation;
import android.app.NotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
......@@ -50,6 +53,7 @@ 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_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 STATIONARY_LOCATION_MONITOR_ACTION = "com.tenforwardconsulting.cordova.bgloc.STATIONARY_LOCATION_MONITOR_ACTION";
private static long STATIONARY_TIMEOUT = 60 * 1000 * 2;
private static final Integer MAX_STATIONARY_ACQUISITION_ATTEMPTS = 3;
private static final Integer MAX_SPEED_ACQUISITION_ATTEMPTS = 3;
......@@ -64,6 +68,7 @@ public class LocationUpdateService extends Service implements LocationListener {
private float stationaryRadius;
private Location stationaryLocation;
private PendingIntent stationaryAlarmPI;
private PendingIntent stationaryLocationMonitorPI;
private PendingIntent stationaryRegionPI;
private PendingIntent singleUpdatePI;
......@@ -86,6 +91,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 +110,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,10 +121,17 @@ 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
stationaryLocationMonitorPI = 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));
// 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();
......@@ -141,6 +156,22 @@ public class LocationUpdateService extends Service implements LocationListener {
desiredAccuracy = Integer.parseInt(intent.getStringExtra("desiredAccuracy"));
locationTimeout = Integer.parseInt(intent.getStringExtra("locationTimeout"));
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 notification = new Notification.Builder(this)
.setContentTitle("Tracking ON")
.setContentText("Content Text")
.setSmallIcon(android.R.drawable.ic_menu_mylocation)
.setContentIntent(pendingIntent)
.build();
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, "- token: " + authToken);
......@@ -151,13 +182,6 @@ public class LocationUpdateService extends Service implements LocationListener {
Log.i(TAG, "- isDebugging: " + isDebugging);
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
return START_REDELIVER_INTENT;
......@@ -200,6 +224,7 @@ public class LocationUpdateService extends Service implements LocationListener {
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);
......@@ -244,7 +269,7 @@ public class LocationUpdateService extends Service implements LocationListener {
* @return The most accurate and / or timely previously detected location.
*/
public Location getLastBestLocation(int minDistance, long minTime) {
Log.i(TAG, "- fetching last best location");
Log.i(TAG, "- fetching last best location " + minDistance + "," + minTime);
Location bestResult = null;
float bestAccuracy = Float.MAX_VALUE;
long bestTime = Long.MIN_VALUE;
......@@ -254,11 +279,13 @@ 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);
Log.d(TAG, location.toString());
if (location != null) {
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;
......@@ -326,6 +353,8 @@ public class LocationUpdateService extends Service implements LocationListener {
scaledDistanceFilter = newDistanceFilter;
setPace(true);
}
} else if (stationaryLocation != null) {
return;
}
// Go ahead and cache, push to server
lastLocation = location;
......@@ -367,9 +396,15 @@ public class LocationUpdateService extends Service implements LocationListener {
location.getLatitude(),
location.getLongitude(),
(location.getAccuracy() < stationaryRadius) ? stationaryRadius : location.getAccuracy(),
-1,
(long)-1,
stationaryRegionPI
);
// 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.
long interval = (long) locationTimeout * 1000;
long start = System.currentTimeMillis() + interval;
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, start, interval, stationaryLocationMonitorPI);
}
/**
......@@ -379,8 +414,9 @@ public class LocationUpdateService extends Service implements LocationListener {
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);
Location location = getLastBestLocation((int) stationaryRadius, System.currentTimeMillis()-(locationTimeout * 1000));
if (location != null) {
// Filter-out spurious region-exits: must have at least a little speed to move out of stationary-region.
......@@ -390,8 +426,13 @@ public class LocationUpdateService extends Service implements LocationListener {
} else {
Log.i(TAG, "- exit stationary region receiver was triggered but could not fetch the last-best location!");
}
// Cancel the periodic stationary location monitor alarm.
alarmManager.cancel(stationaryLocationMonitorPI);
// Kill the current region-monitor we just walked out of.
locationManager.removeProximityAlert(stationaryRegionPI);
// Engage aggressive tracking.
this.setPace(true);
}
......@@ -399,26 +440,33 @@ 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();
// @sound High-pitch beep-beep-beep
toneGenerator.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD);
}
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)
if (location != null) {
Log.d(TAG, "- singleUpdateReciever" + location.toString());
onLocationChanged(location);
}
locationManager.removeUpdates(singleUpdatePI);
}
};
......@@ -430,11 +478,26 @@ 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");
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
*/
......@@ -464,7 +527,7 @@ public class LocationUpdateService extends Service implements LocationListener {
@Override
public void onCellLocationChanged(CellLocation location)
{
onCellLocationChanged(location);
onCellLocationChange(location);
}
};
......@@ -561,9 +624,11 @@ public class LocationUpdateService extends Service implements LocationListener {
private void cleanUp() {
locationManager.removeUpdates(this);
alarmManager.cancel(stationaryAlarmPI);
unregisterReceiver(stationaryAlarmReceiver);
unregisterReceiver(stationaryRegionReceiver);
unregisterReceiver(singleUpdateReceiver);
unregisterReceiver(stationaryRegionReceiver);
unregisterReceiver(stationaryLocationMonitorReceiver);
if (stationaryLocation != null && !isMoving) {
try {
......@@ -572,7 +637,7 @@ public class LocationUpdateService extends Service implements LocationListener {
Log.w(TAG, "- Something bad happened while removing proximity-alert");
}
}
stopForeground(true);
wakeLock.release();
}
......
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