package ca.grocerygo.android.services.location;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;
public class LocationMonitorService extends Service {
private static final String LOCK_NAME_STATIC = "com.grocerygo.android.service.LocationPoller";
private static final int DEFAULT_TIMEOUT = 120000; // two minutes
private static int TIMEOUT = DEFAULT_TIMEOUT;
private static volatile PowerManager.WakeLock lockStatic = null;
private LocationManager locMgr = null;
/**
* Lazy-initializes the WakeLock when we first use it. We
* use a partial WakeLock since we only need the CPU on,
* not the screen.
*/
synchronized private static PowerManager.WakeLock getLock(Context context) {
if (lockStatic == null) {
PowerManager mgr =
(PowerManager) context.getApplicationContext()
.getSystemService(Context.POWER_SERVICE);
lockStatic =
mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
LOCK_NAME_STATIC);
lockStatic.setReferenceCounted(true);
}
return (lockStatic);
}
/**
* Called by LocationPoller to trigger a poll for the
* location. Acquires the WakeLock, then starts the
* service using the supplied Intent (setting the
* component so routing always goes to the service).
*/
public static void requestLocation(Context ctxt, Intent i) {
String provider = i.getStringExtra(LocationMonitor.EXTRA_PROVIDER);
Intent toBroadcast = (Intent) i.getExtras().get(LocationMonitor.EXTRA_INTENT);
if (provider == null) {
Log.e("LocationPoller", "Invalid Intent -- has no provider");
} else if (toBroadcast == null) {
Log.e("LocationPoller", "Invalid Intent -- has no Intent to broadcast");
} else {
getLock(ctxt.getApplicationContext()).acquire();
i.setClass(ctxt, LocationMonitorService.class);
ctxt.startService(i);
}
}
/**
* Obtain the LocationManager on startup
*/
@Override
public void onCreate() {
locMgr = (LocationManager) getSystemService(LOCATION_SERVICE);
}
/**
* No-op implementation as required by superclass
*/
@Override
public IBinder onBind(Intent i) {
return (null);
}
/**
* Validates the required extras (EXTRA_PROVIDER and
* EXTRA_INTENT). If valid, updates the Intent to be
* broadcast with the application's own package (required
* to keep the broadcast within this application, so we do
* not leak security information). Then, forks a
* PollerThread to do the actual location lookup.
*
* @return START_REDELIVER_INTENT to ensure we get the
* last request again
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
PowerManager.WakeLock lock = getLock(this.getApplicationContext());
if (!lock.isHeld() || (flags & START_FLAG_REDELIVERY) != 0) {
lock.acquire();
}
String provider =
intent.getStringExtra(LocationMonitor.EXTRA_PROVIDER);
Intent toBroadcast =
(Intent) intent.getExtras().get(LocationMonitor.EXTRA_INTENT);
TIMEOUT = (int) intent.getLongExtra(LocationMonitor.EXTRA_TIMEOUT,
DEFAULT_TIMEOUT);
toBroadcast.setPackage(getPackageName());
new PollerThread(lock, locMgr, provider, toBroadcast).start();
return (START_REDELIVER_INTENT);
}
/**
* A WakefulThread subclass that knows how to look up the
* current location, plus handle the timeout scenario.
*/
private class PollerThread extends WakefulThread {
private LocationManager locMgr = null;
private String provider = null;
private Intent intentTemplate = null;
private Runnable onTimeout = null;
private LocationListener listener = new LocationListener() {
/**
* If we get a fix, get rid of the timeout condition,
* then attach the location as an extra
* (EXTRA_LOCATION) on the Intent, broadcast it, then
* exit the polling loop so the thread terminates.
*/
public void onLocationChanged(Location location) {
handler.removeCallbacks(onTimeout);
Intent toBroadcast = new Intent(intentTemplate);
toBroadcast.putExtra(LocationMonitor.EXTRA_LOCATION, location);
sendBroadcast(toBroadcast);
quit();
}
public void onProviderDisabled(String provider) {
// required for interface, not used
}
public void onProviderEnabled(String provider) {
// required for interface, not used
}
public void onStatusChanged(String provider, int status,
Bundle extras) {
// required for interface, not used
}
};
private Handler handler = new Handler();
/**
* Constructor.
*
* @param lock Already-locked WakeLock
* @param locMgr LocationManager for doing the location
* lookup
* @param provider name of the location provider to use
* @param intentTemplate Intent to be broadcast when location found
* or timeout occurs
*/
PollerThread(PowerManager.WakeLock lock, LocationManager locMgr,
String provider, Intent intentTemplate) {
super(lock, "LocationPoller-PollerThread");
this.locMgr = locMgr;
this.provider = provider;
this.intentTemplate = intentTemplate;
}
/**
* Called before the Handler loop begins. Registers a
* timeout, so we do not wait forever for a location.
* When a timeout occurs, broadcast an Intent containing
* an error extra, then terminate the thread. Also,
* requests a location update from the LocationManager.
*/
@Override
protected void onPreExecute() {
// Added in enabled check to even check if there is a provider
// enabled.
if (!locMgr.isProviderEnabled(provider)) {
// There is no provider so fail with the LKL if possible
Intent toBroadcast = new Intent(intentTemplate);
toBroadcast.putExtra(LocationMonitor.EXTRA_ERROR,
"Location Provider disabled!");
toBroadcast.putExtra(
LocationMonitor.EXTRA_ERROR_PROVIDER_DISABLED, true);
toBroadcast.putExtra(LocationMonitor.EXTRA_LASTKNOWN,
locMgr.getLastKnownLocation(provider));
sendBroadcast(toBroadcast);
quit();
return;
}
onTimeout = new Runnable() {
public void run() {
Intent toBroadcast = new Intent(intentTemplate);
toBroadcast.putExtra(LocationMonitor.EXTRA_ERROR, "Timeout!");
toBroadcast.putExtra(
LocationMonitor.EXTRA_ERROR_PROVIDER_DISABLED, false);
toBroadcast.putExtra(LocationMonitor.EXTRA_LASTKNOWN,
locMgr.getLastKnownLocation(provider));
sendBroadcast(toBroadcast);
quit();
}
};
handler.postDelayed(onTimeout, TIMEOUT);
try {
locMgr.requestLocationUpdates(provider, 0, 0, listener);
} catch (IllegalArgumentException e) {
// see http://code.google.com/p/android/issues/detail?id=21237
Log.w(getClass().getSimpleName(), "Exception requesting updates -- may be emulator issue", e);
quit();
}
}
/**
* Called when the Handler loop ends. Removes the
* location listener.
*/
@Override
protected void onPostExecute() {
locMgr.removeUpdates(listener);
super.onPostExecute();
}
/**
* Called when the WakeLock is completely unlocked.
* Stops the service, so everything shuts down.
*/
@Override
protected void onUnlocked() {
stopSelf();
}
}
}