package com.twormobile.itrackmygps; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.*; import android.location.*; import android.os.Bundle; import android.preference.PreferenceManager; import com.android.volley.*; import com.twormobile.itrackmygps.android.Log; import com.android.volley.toolbox.StringRequest; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class GpsManager { private static final String TAG = "GpsManager"; protected static String POLL_UPDATE_ACTION = "com.twormobile.itrackmygps.POLL_UPDATE_ACTION"; protected static String SINGLE_LOCATION_UPDATE_ACTION = "com.twormobile.itrackmygps.SINGLE_LOCATION_UPDATE_ACTION"; public static float KPH = 3.6f; // Time Interval in seconds public static final int WALKING_TIME_INTERVAL = 10; public static final int SLOW_DRIVING_TIME_INTERVAL = 30; public static final int MODERATE_DRIVING_TIME_INTERVAL = 60; public static final int FAST_DRIVING_TIME_INTERVAL = 120; // Distance Interval in seconds public static final int ZERO_DISTANCE = 0; public static final int TEN_METERS = 10; public static final int TWENTY_METERS = 20; public static final int ONE_SECOND = 1; public static final int ONE_MINUTE = ONE_SECOND * 60; public static final int TWO_MINUTES = ONE_MINUTE * 2; public static final int FIVE_MINUTES = ONE_MINUTE * 5; private static GpsManager sGpsManager; private static GpsLoggerApplication gpsApp; private Context mAppContext; private LocationManager mLocationManager; private boolean mActive; private boolean mPollUpdateActive = false; private boolean mGpsFixed; private boolean mGpsStatusListenerActive = false; private MyLocationListener networkLocationListener; private MyLocationListener gpsLocationListener; private MyGpsStatusListener gpsStatusListener; protected AlarmManager alarmManager; protected PendingIntent pollUpdatePI; protected PendingIntent singleLocationPI; private String networkProvider; private String gpsProvider; private ArrayList gpsSatelliteList; // loop through satellites to get status private ArrayList<MyLocationListener> locationListeners = new ArrayList(); // list of location listenres ("network", "gps", etc) private int counter = 0; private LocationDatabaseHelper mDatabaseHelper; private Location currentBestLocation; //used for location updates private long minTimeInMilliseconds; private float minDistanceInMeters; private int minTimeInSecondsFromSettings; private int minDistanceInMetersFromSettings; private Criteria criteria = new Criteria(); protected BroadcastReceiver pollUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Create a location intent and register a one shot location receiver IntentFilter locIntentFilter = new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION); mAppContext.registerReceiver(singleLocationUpdateReceiver, locIntentFilter); // Coarse accuracy is specified here to get the fastest possible result. // The calling Activity will likely (or have already) request ongoing // updates using the Fine location provider. criteria.setAccuracy(Criteria.ACCURACY_LOW); // Request a single update from location manager mLocationManager.requestSingleUpdate(criteria, singleLocationPI); } }; protected BroadcastReceiver singleLocationUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Receives a single location update from singleLocationPI String key = LocationManager.KEY_LOCATION_CHANGED; Location location = (Location)intent.getExtras().get(key); // Do routine stuff for location if (gpsLocationListener != null && location != null) gpsLocationListener.onLocationChanged(location); // Remove updates for location manager to conserve batery mLocationManager.removeUpdates(singleLocationPI); // Unregister single update receiver since we register it when the alarm kicks off mAppContext.unregisterReceiver(singleLocationUpdateReceiver); } }; protected GpsManager(){ } // The private constructor forces users to use GpsManager.get(Context) private GpsManager(Context appContext) { mAppContext = appContext; mLocationManager = (LocationManager)mAppContext.getSystemService(Context.LOCATION_SERVICE); mDatabaseHelper = new LocationDatabaseHelper(mAppContext); networkLocationListener = new MyLocationListener(); gpsLocationListener = new MyLocationListener(); gpsStatusListener = new MyGpsStatusListener(); alarmManager = (AlarmManager)mAppContext.getSystemService(Context.ALARM_SERVICE); Intent pollIntent = new Intent(POLL_UPDATE_ACTION); pollUpdatePI = PendingIntent.getBroadcast(mAppContext, 0, pollIntent, PendingIntent.FLAG_UPDATE_CURRENT); Intent locationIntent = new Intent(SINGLE_LOCATION_UPDATE_ACTION); singleLocationPI = PendingIntent.getBroadcast(mAppContext, 0, locationIntent, PendingIntent.FLAG_UPDATE_CURRENT); gpsProvider = LocationManager.GPS_PROVIDER; networkProvider = LocationManager.NETWORK_PROVIDER; // get time and distance interval from preferences SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mAppContext); minTimeInSecondsFromSettings = prefs.getInt(SettingsActivity.PREF_TIME_INTERVAL_IN_SECONDS, SettingsActivity.DEFAULT_TIME_INTERVAL_IN_SECONDS); } public static GpsManager get(Context c) { if (sGpsManager == null) { // Use the application context to avoid leaking activities Log.i(TAG, "onCreated"); gpsApp = (GpsLoggerApplication)c.getApplicationContext(); sGpsManager = new GpsManager(gpsApp); } return sGpsManager; } private boolean isProviderAllowed(String s){ boolean flag = false; for(String provider : mLocationManager.getAllProviders()){ if(provider.contains(s)){ flag = true; break; } } return flag; } public void startLocationUpdates() { // Get the last known gps location and broadcast it if you have one // If you can't find one then broadcast a network location Location lastKnownGPSLocation = mLocationManager.getLastKnownLocation(gpsProvider); if (lastKnownGPSLocation != null) { if(isBetterLocation(lastKnownGPSLocation, currentBestLocation)){ currentBestLocation = lastKnownGPSLocation; broadcastLocation(lastKnownGPSLocation); } } else{ Location lastKnownNetworkLocation = mLocationManager.getLastKnownLocation(networkProvider); if(lastKnownNetworkLocation != null){ if(isBetterLocation(lastKnownNetworkLocation, currentBestLocation)){ currentBestLocation = lastKnownNetworkLocation; broadcastLocation(lastKnownNetworkLocation); } } } startLocationProviders(); broadcastGpsNetworkStatus(); } public void startLocationProviders(){ // If we have WIFI then it means we are at home or indoors if(gpsApp.isWiFiConnected()) { startPollingAfterFiveMinutes(); } else { startActivePolling(); } } public void startPollingAfterFiveMinutes() { int seconds = FIVE_MINUTES; if(minTimeInSecondsFromSettings > FIVE_MINUTES) { seconds = minTimeInSecondsFromSettings; } // We only register a POLL_UPDATE_ACTION intent for pollUpdateReceiver // if it is not active by checking mPollUpdateActive if(!mPollUpdateActive) { long kickInTime = System.currentTimeMillis() + ONE_SECOND * 1000L; long intervalTime = seconds * 1000L; alarmManager.setInexactRepeating(AlarmManager.RTC, kickInTime, intervalTime, pollUpdatePI); IntentFilter intentFilter = new IntentFilter(POLL_UPDATE_ACTION); mAppContext.registerReceiver(pollUpdateReceiver, intentFilter); mPollUpdateActive = true; // Request a single update immediately from location manager criteria.setAccuracy(Criteria.ACCURACY_LOW); mLocationManager.requestSingleUpdate(criteria, singleLocationPI); broadcastTimeIntervalChange(intervalTime); mActive = true; } } public void startActivePolling() { startPolling(WALKING_TIME_INTERVAL, TWENTY_METERS); } private void startPolling(int time_interval, int distance) { minTimeInMilliseconds = time_interval * 1000L; minDistanceInMeters = distance; startListenerForProvider(networkLocationListener, networkProvider); startListenerForProvider(gpsLocationListener, gpsProvider); if(!mGpsStatusListenerActive) { mLocationManager.addGpsStatusListener(gpsStatusListener); mGpsStatusListenerActive = true; } mActive = true; } private void startListenerForProvider(MyLocationListener listener, String provider){ if(isProviderAllowed(provider) && mLocationManager.isProviderEnabled(provider)){ if(locationListeners.contains(listener) == false) { // http://developer.android.com/reference/android/location/LocationManager.html // If it is greater than 0 then the location provider will only send your application an update when the // location has changed by at least minDistance meters, AND at least minTime milliseconds have passed. mLocationManager.requestLocationUpdates(provider, minTimeInMilliseconds, minDistanceInMeters, listener); locationListeners.add(listener); if(provider == gpsProvider && ApplicationConstants.DEBUG) { gpsApp.showToast("Interval every " + minTimeInMilliseconds/1000L + " secs and " + minDistanceInMeters + " m"); } broadcastTimeIntervalChange(minTimeInMilliseconds); } } } public void stopLocationProviders() { stopListenerForProvider(networkLocationListener); stopListenerForProvider(gpsLocationListener); if(mGpsStatusListenerActive) { mLocationManager.removeGpsStatusListener(gpsStatusListener); mGpsStatusListenerActive = false; } // Ensure that we check if poll update is active before // unregistering or it will throw an IllegalArgumentException if (alarmManager != null && mPollUpdateActive) { // Unregister the poll update receiver since it will re-register mAppContext.unregisterReceiver(pollUpdateReceiver); mPollUpdateActive = false; alarmManager.cancel(pollUpdatePI); } mActive = false; mGpsFixed = false; broadcastGpsNetworkStatus(); } private void stopListenerForProvider(LocationListener listener) { // Safely removeUpdates for the LocationListener if(locationListeners.contains(listener)){ mLocationManager.removeUpdates(listener); locationListeners.remove(listener); } } /** * Adjust time and distance interval for requestLocationUpdate * * @param seconds Seconds for time delay. * @param meters Meters for distance delay. */ public void adjustGpsUpdateInterval(int seconds, int meters){ if(isGPSActive()){ minTimeInMilliseconds = seconds * 1000L; minDistanceInMeters = meters * 1.0f; stopListenerForProvider(gpsLocationListener); startListenerForProvider(gpsLocationListener, gpsProvider); broadcastTimeIntervalChange(minTimeInMilliseconds); } } /** * Returns true if the location listeners are running and getting active updates from onLocationChanged * */ public boolean isGPSActive() { return mActive; } /** * Broadcast the location to receivers. Afterwards, insert the location to the database and post the location via HTTP. * * @param location The accepted location from onLocationChanged. */ private void broadcastLocation(Location location) { if(location != null){ counter++; Intent broadcast = new Intent(IntentCodes.ACTION_LOCATION); broadcast.putExtra(LocationManager.KEY_LOCATION_CHANGED, location); broadcast.putExtra("counter", counter); mAppContext.sendBroadcast(broadcast); insertLocation(location); postLocation(location); } } private void broadcastGpsNetworkStatus() { Intent broadcast = new Intent(IntentCodes.ACTION_GPS_NETWORK_STATUS); int index = connectionStatus().ordinal(); broadcast.putExtra("GPS_NETWORK_STATUS", index); mAppContext.sendBroadcast(broadcast); } private void broadcastTimeIntervalChange(long milliseconds){ Intent broadcast = new Intent(IntentCodes.ACTION_TIME_INTERVAL_CHANGE); String str = ""; int seconds = (int) (milliseconds/ (1000L)); if(seconds < 60){ str = seconds + " secs"; } else { str = (seconds / 60) + " min"; } broadcast.putExtra("TIME_INTERVAL", str); mAppContext.sendBroadcast(broadcast); } public void displayCurrentTimeInterval(){ if(mPollUpdateActive) broadcastTimeIntervalChange(minTimeInSecondsFromSettings*1000L); else broadcastTimeIntervalChange(minTimeInMilliseconds); } public void insertLocation(Location location){ mDatabaseHelper.insertLocation(location); } public void postLocation(final Location location){ if(location == null) return; final String url = gpsApp.LOCATION_NEW_URL; final String timestamp = Long.toString(location.getTime()); final String latitude = Double.toString(location.getLatitude()); final String longitude = Double.toString(location.getLongitude()); final String speedInKPH = Float.toString(location.getSpeed()*KPH); final String heading = Float.toString(location.getBearing()); final String provider = location.getProvider(); final String timeInterval = Integer.toString((int) minTimeInMilliseconds); final String distanceInterval = Integer.toString((int) minDistanceInMeters); StringRequest postRequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>(){ @Override public void onResponse(String response){ VolleyLog.v("Response:%n %s", response); } }, new Response.ErrorListener(){ @Override public void onErrorResponse(VolleyError error){ Log.d(TAG, "Error on " + url); VolleyLog.e("Error: ", error.getMessage()); } }) { @Override protected Map<String, String> getParams() { Map<String, String> params = new HashMap<String, String>(); params.put("uuid", gpsApp.getUUID()); params.put("gps_timestamp", timestamp); params.put("gps_latitude", latitude); params.put("gps_longitude", longitude); params.put("gps_speed", speedInKPH); params.put("gps_heading", heading); params.put("provider", provider); params.put("time_interval", timeInterval); params.put("distance_interval", distanceInterval); return params; } }; postRequest.setRetryPolicy(new DefaultRetryPolicy(20 * 1000, 1, 1.0f)); VolleySingleton.getInstance(mAppContext).addToRequestQueue(postRequest); } // Methods in this class are called when the location providers give an update private class MyLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { mActive = true; if(isBetterLocation(location, currentBestLocation)){ currentBestLocation = location; broadcastLocation(location); } // Adjust minTime and minDistance based on Speed if(location.getProvider().equals(LocationManager.GPS_PROVIDER)) { int currentTimeIntervalInSeconds = (int) (minTimeInMilliseconds / 1000L); int seconds = currentTimeIntervalInSeconds; boolean mChange = false; float speedInKPH = location.getSpeed()*KPH; // Speed is slow if(speedInKPH >= 20 && speedInKPH < 60) { seconds = SLOW_DRIVING_TIME_INTERVAL; mChange = true; } else if(speedInKPH >= 60 && speedInKPH < 100) { seconds = MODERATE_DRIVING_TIME_INTERVAL; mChange = true; } // Speed is high else if(speedInKPH > 100) { seconds = FAST_DRIVING_TIME_INTERVAL; mChange = true; } // Only adjust the interval if the current time interval is different from the new time interval if(mChange && currentTimeIntervalInSeconds != seconds) { adjustGpsUpdateInterval(seconds, (int) minDistanceInMeters); } } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } } // Methods in this class are called when the status of the GPS changes private class MyGpsStatusListener implements GpsStatus.Listener { // called to handle an event updating the satellite status private void satelliteStatusUpdate() { // use the location manager to get a gps status object // this method should only be called inside GpsStatus.Listener GpsStatus gpsStatus = mLocationManager.getGpsStatus(null); // create an iterator to loop through list of satellites Iterable<GpsSatellite> iSatellites = gpsStatus.getSatellites(); Iterator<GpsSatellite> gpsSatelliteIterator = iSatellites.iterator(); // find the satellite with the best (greatest signal to noise ratio to update display // and save list of satellites in an ArrayList gpsSatelliteList = new ArrayList<GpsSatellite>(); while (gpsSatelliteIterator.hasNext()){ // get next satellite from iterator GpsSatellite s = (GpsSatellite) gpsSatelliteIterator.next(); // and add to ArrayList gpsSatelliteList.add(s); } } // the status of the GPS has changed public void onGpsStatusChanged(int event) { switch (event) { case GpsStatus.GPS_EVENT_SATELLITE_STATUS: Log.d(TAG, "GPS_EVENT_SATELLITE_STATUS"); satelliteStatusUpdate(); break; case GpsStatus.GPS_EVENT_STARTED: Log.d(TAG, "GPS_EVENT_STARTED"); break; case GpsStatus.GPS_EVENT_FIRST_FIX: Log.d(TAG, "GPS_EVENT_FIRST_FIX"); gpsApp.showToast("Received First Fix"); stopListenerForProvider(networkLocationListener); break; case GpsStatus.GPS_EVENT_STOPPED: Log.d(TAG, "GPS_EVENT_STOPPED"); break; } } }; public boolean isLocationAccessEnabled(){ return mLocationManager.isProviderEnabled(gpsProvider); } /** Determines whether one Location reading is better than the current Location fix * @param location The new Location that you want to evaluate * @param bestLocation The current Location fix, to which you want to compare the new one */ protected boolean isBetterLocation(Location location, Location bestLocation) { if (bestLocation == null) { // A new location is always better than no location return true; } // Check whether the new location fix is newer or older long timeDelta = location.getTime() - bestLocation.getTime(); boolean isSameTime = timeDelta == 0; if (isSameTime && isSameLocation(location, bestLocation)) { return false; } boolean isSignificantlyNewer = timeDelta > (TWO_MINUTES * 1000L); boolean isSignificantlyOlder = timeDelta < (-TWO_MINUTES * 1000L); boolean isNewer = timeDelta > 0; // If it's been more than two minutes since the current location, use the new location // because the user has likely moved if (isSignificantlyNewer) { return true; // If the new location is more than two minutes older, it must be worse } else if (isSignificantlyOlder) { return false; } // Check whether the new location fix is more or less accurate int accuracyDelta = (int) (location.getAccuracy() - bestLocation.getAccuracy()); boolean isLessAccurate = accuracyDelta > 0; boolean isMoreAccurate = accuracyDelta < 0; boolean isSignificantlyLessAccurate = accuracyDelta > 200; // Check if the old and new location are from the same provider boolean isFromSameProvider = isSameProvider(location.getProvider(), bestLocation.getProvider()); // Determine location quality using a combination of timeliness and accuracy if (isMoreAccurate) { setGpsFixStatusIf(location); return true; } else if (isNewer && !isLessAccurate) { setGpsFixStatusIf(location); return true; } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { setGpsFixStatusIf(location); return true; } return false; } /** Checks whether two providers are the same */ private boolean isSameProvider(String provider1, String provider2) { if (provider1 == null) { return provider2 == null; } return provider1.equals(provider2); } /** Checks whether two gps locations are the same */ protected boolean isSameLocation(Location loc1, Location loc2) { if(loc1 != null && loc2 != null) { return loc1.getLatitude() == loc2.getLatitude() && loc1.getLongitude() == loc2.getLongitude(); } else { return true; } } public void updateFromSettings(int secs){ minTimeInSecondsFromSettings = secs; if(isGPSActive()){ stopLocationProviders(); startLocationProviders(); } } public void setGpsFixStatusIf(Location location){ if(!mGpsFixed && location.getProvider().equals("gps")){ mGpsFixed = true; broadcastGpsNetworkStatus(); } } public GpsFix connectionStatus(){ if(mActive){ if(mGpsFixed){ return GpsFix.CONNECTED; } else{ return GpsFix.ACQUIRING_FIX; } } else{ return GpsFix.IDLE; } } public int getTotalSatellites(){ if(gpsSatelliteList != null) { return gpsSatelliteList.size(); } else return 0; } /** * Returns the number of location updates received from location listeners. */ public int getCounter() { return counter; } /** * Returns currentLocation */ public Location getCurrentLocation() { return currentBestLocation; } }