Sapan Diwakar

Software developer

Follow me on Twitter Check out my code on GitHub View some of my designs on Dribbble Take a look at my Linked In profile

Obtaining user location from mobile device with Android

I recently built an application called HowSoon for Android. I already did a blog entry earlier announcing HowSoon, so if you don't know what it is, have a look at it here. In short, it is an app that gives the next departures from public transport stations near you.

I will glance through some of the tecnical details about HowSoon in this blog. The most important requirement for HowSoon is to be able to obtain user's location quickly in order to bring him the departueres tailored for his current location. There are several possible ways of obtaining user's location from a mobile device as described in this guide. We have to go through the LocationManager to request location updates from the mobile device. With HowSoon, we are more concerned with having the location available as soon as possible rather than having the most accurate location. For this, we use queue location requests for GPS as well as network location with the location manager and use the one that we can obtain the earliest. We also put a timeout of 2 seconds on the requests and fall back to using the user's last known location if we cannot find the current location quickly enough.

public void getLocation(Context context, LocationResult result) {  
  LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

  // Exceptions will be thrown if provider is not permitted.
  try {
    isGpsEnabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
  } catch (Exception ex) {
    // Handle exception
  }

  try {
    isNetworkLocationEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
  } catch (Exception ex) {
    // Handle exception
  }

  // Queue GPS location request
  if (isGpsEnabled)
    lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps);

  // Queue network location request
  if (isNetworkLocationEnabled)
    lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork);

  // Create a task that retreives the last known location for the user in case we can't get the current location.
  lastLocationRunable = new GetLastLocation(); 

  if (!isNetworkLocationEnabled && !isGpsEnabled) {
    // Both network and GPS are disabled. 
    // We need to ask the user to enable at least one of them
  } else {
    // Use the last known location if we cannot obtain the user's location in 2000 msec
    handler = new Handler();
    handler.postDelayed(lastLocationRunable, 2000);
  }

}

public void cancelTimer() {  
  handler.removeCallbacks(lastLocationRunable);
  lm.removeUpdates(locationListenerGps);
  lm.removeUpdates(locationListenerNetwork);
}

LocationListener locationListenerGps = new LocationListener() {  
  public void onLocationChanged(Location location) {
    cancelTimer(); // Cancels the timer
    locationResult.gotLocation(location);
    lm.removeUpdates(this);
    lm.removeUpdates(locationListenerNetwork);
  }

  public void onProviderDisabled(String provider) {
  }

  public void onProviderEnabled(String provider) {
  }

  public void onStatusChanged(String provider, int status, Bundle extras) {
  }
};

LocationListener locationListenerNetwork = new LocationListener() {  
  public void onLocationChanged(Location location) {
    cancelTimer(); // Cancels the timer
    locationResult.gotLocation(location);
    lm.removeUpdates(this);
    lm.removeUpdates(locationListenerGps);
  }

  public void onProviderDisabled(String provider) {
  }

  public void onProviderEnabled(String provider) {
  }

  public void onStatusChanged(String provider, int status, Bundle extras) {
  }
};

class GetLastLocation implements Runnable {

  @Override
  public void run() {
    Log.i(TAG, "Using last location");
    lm.removeUpdates(locationListenerGps);
    lm.removeUpdates(locationListenerNetwork);

    // Try to get last known gps location
    Location networkLocation = null, gpsLocation = null;
    if (isGpsEnabled)
      gpsLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
    // Try to get last known network location
    if (isNetworkLocationEnabled)
      networkLocation = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

    // if there are both values use the latest one
    if (gpsLocation != null && networkLocation != null) {
      if (gpsLocation.getTime() > networkLocation.getTime())
        locationResult.gotLocation(gpsLocation);
      else
        locationResult.gotLocation(networkLocation);
      return;
    }

    if (gpsLocation != null) {
      locationResult.gotLocation(gpsLocation);
      return;
    }
    if (networkLocation != null) {
      locationResult.gotLocation(networkLocation);
      return;
    }
    locationResult.gotLocation(null);
  }
}

public static abstract class LocationResult {  
  public abstract void gotLocation(Location location);
}

The usage is pretty simple. All we need is to make a call to the getLocation and supply it a callback which will be invoked when the location is available.

In order to receive location updates from NETWORK_PROVIDER or GPS_PROVIDER, you must request user permission by declaring either the ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission, respectively, in your Android manifest file. For example:

<manifest ... >  
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
</manifest>