com.marianhello.cordova.bgloc.LocationUpdateService.java Source code

Java tutorial

Introduction

Here is the source code for com.marianhello.cordova.bgloc.LocationUpdateService.java

Source

    /*
    According to apache license
    
    This is fork of christocracy cordova-plugin-background-geolocation plugin
    https://github.com/christocracy/cordova-plugin-background-geolocation
    
    Differences to original version:
    
    1. To avoid conflicts
    package com.tenforwardconsulting.cordova.bgloc
    was renamed to com.marianhello.cordova.bgloc
    
    2. location is not persisted to db anymore, but broadcasted using intents instead
    */

    package com.marianhello.cordova.bgloc;

    import java.util.List;
    import java.util.Iterator;

    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.marianhello.cordova.bgloc.data.DAOFactory;
    import com.marianhello.cordova.bgloc.data.LocationDAO;

    import android.annotation.TargetApi;

    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.support.v4.content.LocalBroadcastManager;

    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 org.json.JSONException;

    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.marianhello.cordova.bgloc.STATIONARY_REGION_ACTION";
        private static final String STATIONARY_ALARM_ACTION = "com.marianhello.cordova.bgloc.STATIONARY_ALARM_ACTION";
        private static final String SINGLE_LOCATION_UPDATE_ACTION = "com.marianhello.cordova.bgloc.SINGLE_LOCATION_UPDATE_ACTION";
        private static final String STATIONARY_LOCATION_MONITOR_ACTION = "com.marianhello.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 JSONObject params;
        private JSONObject headers;
        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 locationAcquisitionAttempts = 0;

        private Integer desiredAccuracy = 100;
        private Integer distanceFilter = 30;
        private Integer scaledDistanceFilter;
        private Integer locationTimeout = 30;
        private Boolean isDebugging;
        private String notificationTitle = "Background checking";
        private String notificationText = "ENABLED";
        private Boolean stopOnTerminate;

        private ToneGenerator toneGenerator;

        private Criteria criteria;

        private LocationManager locationManager;
        private AlarmManager alarmManager;
        private ConnectivityManager connectivityManager;
        private NotificationManager notificationManager;
        public static TelephonyManager telephonyManager = null;

        @Override
        public IBinder onBind(Intent intent) {
            // TODO Auto-generated method stub
            Log.i(TAG, "OnBind" + intent);
            return null;
        }

        @Override
        public void onCreate() {
            super.onCreate();
            Log.i(TAG, "OnCreate");

            locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
            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);
            registerReceiver(stationaryAlarmReceiver, new IntentFilter(STATIONARY_ALARM_ACTION));

            // Stationary region PI
            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_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
            criteria = new Criteria();
            criteria.setAltitudeRequired(false);
            criteria.setBearingRequired(false);
            criteria.setSpeedRequired(true);
            criteria.setCostAllowed(true);
        }

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i(TAG, "Received start id " + startId + ": " + intent);
            if (intent != null) {
                try {
                    params = new JSONObject(intent.getStringExtra("params"));
                    headers = new JSONObject(intent.getStringExtra("headers"));
                } 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"));
                scaledDistanceFilter = distanceFilter;
                desiredAccuracy = Integer.parseInt(intent.getStringExtra("desiredAccuracy"));
                locationTimeout = Integer.parseInt(intent.getStringExtra("locationTimeout"));
                isDebugging = Boolean.parseBoolean(intent.getStringExtra("isDebugging"));
                notificationTitle = intent.getStringExtra("notificationTitle");
                notificationText = intent.getStringExtra("notificationText");

                // 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(notificationTitle);
                builder.setContentText(notificationText);
                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, "- params: " + params.toString());
            Log.i(TAG, "- headers: " + headers.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);
            Log.i(TAG, "- notificationTitle: " + notificationTitle);
            Log.i(TAG, "- notificationText: " + notificationText);

            this.setPace(false);

            //We want this service to continue running until it is explicitly stopped
            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
        public boolean stopService(Intent intent) {
            Log.i(TAG, "- Received stop: " + intent);
            cleanUp();
            if (isDebugging) {
                Toast.makeText(this, "Background location tracking stopped", Toast.LENGTH_SHORT).show();
            }
            return super.stopService(intent);
        }

        /**
         *
         * @param value set true to engage "aggressive", battery-consuming tracking, false for stationary-region tracking
         */
        private void setPace(Boolean value) {
            Log.i(TAG, "setPace: " + value);

            Boolean wasMoving = isMoving;
            isMoving = value;
            isAcquiringStationaryLocation = false;
            isAcquiringSpeed = false;
            stationaryLocation = null;

            locationManager.removeUpdates(this);

            criteria.setAccuracy(Criteria.ACCURACY_FINE);
            criteria.setHorizontalAccuracy(translateDesiredAccuracy(desiredAccuracy));
            criteria.setPowerRequirement(Criteria.POWER_HIGH);

            if (isMoving) {
                // 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;
                }
            } else {
                isAcquiringStationaryLocation = true;
            }

            // Temporarily turn on super-aggressive geolocation on all providers when acquiring velocity or stationary location.
            if (isAcquiringSpeed || isAcquiringStationaryLocation) {
                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);
            }
        }

        /**
        * Translates a number representing desired accuracy of GeoLocation system from set [0, 10, 100, 1000].
        * 0:  most aggressive, most accurate, worst battery drain
        * 1000:  least aggressive, least accurate, best for battery.
        */
        private Integer translateDesiredAccuracy(Integer accuracy) {
            switch (accuracy) {
            case 1000:
                accuracy = Criteria.ACCURACY_LOW;
                break;
            case 100:
                accuracy = Criteria.ACCURACY_MEDIUM;
                break;
            case 10:
                accuracy = Criteria.ACCURACY_HIGH;
                break;
            case 0:
                accuracy = Criteria.ACCURACY_HIGH;
                break;
            default:
                accuracy = Criteria.ACCURACY_MEDIUM;
            }
            return accuracy;
        }

        /**
         * Returns the most accurate and timely previously detected location.
         * Where the last result is beyond the specified maximum distance or
         * latency a one-off location update is returned via the {@link LocationListener}
         * specified in {@link setChangedLocationListener}.
         * @param minDistance Minimum distance before we require a location update.
         * @param minTime Minimum time required between location updates.
         * @return The most accurate and / or timely previously detected 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;

            // Iterate through all the providers on the system, keeping
            // note of the most accurate result within the acceptable time limit.
            // 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;
                    }
                }
            }
            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();
            }
            if (isAcquiringStationaryLocation) {
                if (stationaryLocation == null || stationaryLocation.getAccuracy() > location.getAccuracy()) {
                    stationaryLocation = location;
                }
                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) {
                if (++locationAcquisitionAttempts == MAX_SPEED_ACQUISITION_ATTEMPTS) {
                    // Got enough samples, assume we're confident in reported speed now.  Play "woohoo" sound.
                    if (isDebugging) {
                        startTone("doodly_doo");
                    }
                    isAcquiringSpeed = false;
                    scaledDistanceFilter = calculateDistanceFilter(location.getSpeed());
                    setPace(true);
                } else {
                    if (isDebugging) {
                        startTone("beep");
                    }
                    return;
                }
            } else if (isMoving) {
                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.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;
            // persistLocation(location);
            broadcastLocation(location);

            // if (this.isNetworkConnected()) {
            //     Log.d(TAG, "Scheduling location network post");
            //     schedulePostLocations();            

            // } else {
            //     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
        }

        private Integer calculateDistanceFilter(Float speed) {
            Double newDistanceFilter = (double) distanceFilter;
            if (speed < 100) {
                float roundedDistanceFilter = (round(speed / 5) * 5);
                newDistanceFilter = pow(roundedDistanceFilter, 2) + (double) distanceFilter;
            }
            return (newDistanceFilter.intValue() < 1000) ? newDistanceFilter.intValue() : 1000;
        }

        private void startMonitoringStationaryRegion(Location location) {
            locationManager.removeUpdates(this);
            stationaryLocation = location;

            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(), (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;
            }
            if (isDebugging) {
                startTone("beep");
            }
            float distance = abs(location.distanceTo(stationaryLocation) - stationaryLocation.getAccuracy()
                    - location.getAccuracy());

            if (isDebugging) {
                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(Location location) {
            // Filter-out spurious region-exits:  must have at least a little speed to move out of stationary-region
            if (isDebugging) {
                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);
        }

        /**
        * TODO Experimental cell-tower change system; something like ios significant changes.
        */
        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.
        */
        private BroadcastReceiver singleUpdateReceiver=new BroadcastReceiver(){@Override public void onReceive(Context context,Intent intent){String key=LocationManager.KEY_LOCATION_CHANGED;Location location=(Location)intent.getExtras().get(key);if(location!=null){Log.d(TAG,"- singleUpdateReciever"+location.toString());onPollStationaryLocation(location);}}};

        /**
        * Broadcast receiver which detcts a user has stopped for a long enough time to be determined as STOPPED
        */
        private BroadcastReceiver stationaryAlarmReceiver=new BroadcastReceiver(){@Override public void onReceive(Context context,Intent intent){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
        */
        private BroadcastReceiver stationaryRegionReceiver=new BroadcastReceiver(){@Override public void onReceive(Context context,Intent intent){Log.i(TAG,"stationaryRegionReceiver");String key=LocationManager.KEY_PROXIMITY_ENTERING;

        Boolean entering=intent.getBooleanExtra(key,false);if(entering){Log.d(TAG,"- ENTER");if(isMoving){setPace(false);}}else{Log.d(TAG,"- EXIT");
        // There MUST be a valid, recent location if this event-handler was called.
        Location location=getLastBestLocation();if(location!=null){onExitStationaryRegion(location);}}}};
        /**
        * TODO Experimental, hoping to implement some sort of "significant changes" system here like ios based upon cell-tower changes.
        */
        private PhoneStateListener phoneStateListener=new PhoneStateListener(){@Override public void onCellLocationChanged(CellLocation 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());

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            else
                task.execute();
            Log.d(TAG, "afterexecute " + task.getStatus());
        }

        private boolean postLocation(com.marianhello.cordova.bgloc.data.Location l, LocationDAO dao) {
            if (l == null) {
                Log.w(TAG, "postLocation: null location");
                return false;
            }
            try {
                lastUpdateTime = SystemClock.elapsedRealtime();
                Log.i(TAG, "Posting  native location update: " + l);
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpPost request = new HttpPost(url);

                JSONObject location = new JSONObject();
                location.put("latitude", l.getLatitude());
                location.put("longitude", l.getLongitude());
                location.put("accuracy", l.getAccuracy());
                location.put("speed", l.getSpeed());
                location.put("bearing", l.getBearing());
                location.put("altitude", l.getAltitude());
                location.put("recorded_at", dao.dateToString(l.getRecordedAt()));
                params.put("location", location);

                Log.i(TAG, "location: " + location.toString());

                StringEntity se = new StringEntity(params.toString());
                request.setEntity(se);
                request.setHeader("Accept", "application/json");
                request.setHeader("Content-type", "application/json");

                Iterator<String> headkeys = headers.keys();
                while (headkeys.hasNext()) {
                    String headkey = headkeys.next();
                    if (headkey != null) {
                        Log.d(TAG, "Adding Header: " + headkey + " : " + (String) headers.getString(headkey));
                        request.setHeader(headkey, (String) headers.getString(headkey));
                    }
                }
                Log.d(TAG, "Posting to " + request.getURI().toString());
                HttpResponse response = httpClient.execute(request);
                Log.i(TAG, "Response received: " + response.getStatusLine());
                if (response.getStatusLine().getStatusCode() == 200) {
                    return true;
                } else {
                    return false;
                }
            } catch (Throwable e) {
                Log.w(TAG, "Exception posting location: " + e);
                e.printStackTrace();
                return false;
            }
        }

        private void persistLocation(Location location) {
            LocationDAO dao = DAOFactory.createLocationDAO(this.getApplicationContext());
            com.marianhello.cordova.bgloc.data.Location savedLocation = com.marianhello.cordova.bgloc.data.Location
                    .fromAndroidLocation(location);

            if (dao.persistLocation(savedLocation)) {
                Log.d(TAG, "Persisted Location: " + savedLocation);
            } else {
                Log.w(TAG, "Failed to persist location");
            }
        }

        private void broadcastLocation(Location location) {
            Log.d(TAG, "Broadcasting update message: " + location.toString());
            try {
                String locStr = com.marianhello.cordova.bgloc.data.Location.fromAndroidLocation(location).toJSONObject()
                        .toString();
                Intent intent = new Intent(Constant.FILTER);
                intent.putExtra(Constant.COMMAND, Constant.UPDATE_PROGRESS);
                // intent.putExtra(Constant.DATA, location);
                intent.putExtra(Constant.DATA, locStr);
                // LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
                this.sendBroadcast(intent);
            } catch (JSONException e) {
                Log.w(TAG, "Failed to broadcast location");
            }
        }

        private boolean isNetworkConnected() {
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            if (networkInfo != null) {
                Log.d(TAG, "Network found, type = " + networkInfo.getTypeName());
                return networkInfo.isConnected();
            } else {
                Log.d(TAG, "No active network info");
                return false;
            }
        }

        @Override
        public void onDestroy() {
            Log.w(TAG, "------------------------------------------ Destroyed Location update Service");
            cleanUp();
            super.onDestroy();
        }

        private void cleanUp() {
            locationManager.removeUpdates(this);
            alarmManager.cancel(stationaryAlarmPI);
            alarmManager.cancel(stationaryLocationPollingPI);
            toneGenerator.release();

            unregisterReceiver(stationaryAlarmReceiver);
            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();
        }

        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        @Override
        public void onTaskRemoved(Intent rootIntent) {
            this.stopSelf();
            super.onTaskRemoved(rootIntent);
        }

        private class PostLocationTask extends AsyncTask<Object, Integer, Boolean> {

            @Override
            protected Boolean doInBackground(Object... objects) {
                Log.d(TAG, "Executing PostLocationTask#doInBackground");
                LocationDAO locationDAO = DAOFactory
                        .createLocationDAO(LocationUpdateService.this.getApplicationContext());
                for (com.marianhello.cordova.bgloc.data.Location savedLocation : locationDAO.getAllLocations()) {
                    Log.d(TAG, "Posting saved location");
                    if (postLocation(savedLocation, locationDAO)) {
                        locationDAO.deleteLocation(savedLocation);
                    }
                }
                return true;
            }

            @Override
            protected void onPostExecute(Boolean result) {
                Log.d(TAG, "PostLocationTask#onPostExecture");
            }
        }
    }