title: Good way of getting the user's location in Android
body: <h2>The problem:</h2>
  
  <p>Getting the user's current location within a threshold ASAP and at the same time conserve battery.</p>
  
  <h2>Why the problem is a problem:</h2>
  
  <p>First off, android has two providers; network and GPS. Sometimes network is better and sometimes the GPS is better.</p>
  
  <p>By "better" I mean speed vs. accuracy ratio.<br>
  I'm willing to sacrifice a few meters in accuracy if I can get the location almost instant and without turning on the GPS.</p>
  
  <p>Secondly, if you request updates for location changes nothing is sent if the current location is stable.</p>
  
  <p>Google has an example of determining the "best" location here: <a href="http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate" rel="noreferrer">http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate</a><br>
  But I think it's no where near as good as it should/could be.</p>
  
  <p>I'm kind of confused why google hasn't a normalized API for location, the developer shouldn't have to care where the location is from, you should just specify what you want and the phone should choose for you.</p>
  
  <h2>What I need help with:</h2>
  
  <p>I need to find a good way to determine the "best" location, maybe though some heuristic or maybe through some 3rd party library.</p>
  
  <p><b>This does not mean determine the best provider!</b><br>
  I'm probably gonna use all providers and picking the best of them.</p>
  
  <h2>Background of the app:</h2>
  
  <p>The app will collect the user's location at a fixed interval (let say every 10 minutes or so) and send it to a server.<br>
  The app should conserve as much battery as possible and the location should have X (50-100?) meters accuracy.</p>
  
  <p>The goal is to later be able to plot the user's path during the day on a map so I need sufficient accuracy for that.</p>
  
  <h2>Misc:</h2>
  
  <p>What do you think are reasonable values on desired and accepted accuracies?<br>
  I've been using 100m as accepted and 30m as desired, is this to much to ask?<br>
  I'd like to be able to plot the user's path on a map later.<br>
  Is 100m for desired and 500m for accepted better?</p>
  
  <p>Also, right now I have the GPS on for a maximum of 60 seconds per location update, is this too short to get a location if you're indoors with an accuracy of maybe 200m?</p>
  
  <hr>
  
  <p>This is my current code, any feedback is appreciated (apart from the lack of error checking which is TODO):</p>
  
  <pre><code>protected void runTask() {
      final LocationManager locationManager = (LocationManager) context
              .getSystemService(Context.LOCATION_SERVICE);
      updateBestLocation(locationManager
              .getLastKnownLocation(LocationManager.GPS_PROVIDER));
      updateBestLocation(locationManager
              .getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
      if (getLocationQuality(bestLocation) != LocationQuality.GOOD) {
          Looper.prepare();
          setLooper(Looper.myLooper());
          // Define a listener that responds to location updates
          LocationListener locationListener = new LocationListener() {
  
              public void onLocationChanged(Location location) {
                  updateBestLocation(location);
                  if (getLocationQuality(bestLocation) != LocationQuality.GOOD)
                      return;
                  // We're done
                  Looper l = getLooper();
                  if (l != null) l.quit();
              }
  
              public void onProviderEnabled(String provider) {}
  
              public void onProviderDisabled(String provider) {}
  
              public void onStatusChanged(String provider, int status,
                      Bundle extras) {
                  // TODO Auto-generated method stub
                  Log.i("LocationCollector", "Fail");
                  Looper l = getLooper();
                  if (l != null) l.quit();
              }
          };
          // Register the listener with the Location Manager to receive
          // location updates
          locationManager.requestLocationUpdates(
                  LocationManager.GPS_PROVIDER, 1000, 1, locationListener,
                  Looper.myLooper());
          locationManager.requestLocationUpdates(
                  LocationManager.NETWORK_PROVIDER, 1000, 1,
                  locationListener, Looper.myLooper());
          Timer t = new Timer();
          t.schedule(new TimerTask() {
  
              @Override
              public void run() {
                  Looper l = getLooper();
                  if (l != null) l.quit();
                  // Log.i("LocationCollector",
                  // "Stopping collector due to timeout");
              }
          }, MAX_POLLING_TIME);
          Looper.loop();
          t.cancel();
          locationManager.removeUpdates(locationListener);
          setLooper(null);
      }
      if (getLocationQuality(bestLocation) != LocationQuality.BAD) 
          sendUpdate(locationToString(bestLocation));
      else Log.w("LocationCollector", "Failed to get a location");
  }
  
  private enum LocationQuality {
      BAD, ACCEPTED, GOOD;
  
      public String toString() {
          if (this == GOOD) return "Good";
          else if (this == ACCEPTED) return "Accepted";
          else return "Bad";
      }
  }
  
  private LocationQuality getLocationQuality(Location location) {
      if (location == null) return LocationQuality.BAD;
      if (!location.hasAccuracy()) return LocationQuality.BAD;
      long currentTime = System.currentTimeMillis();
      if (currentTime - location.getTime() &lt; MAX_AGE
              &amp;&amp; location.getAccuracy() &lt;= GOOD_ACCURACY)
          return LocationQuality.GOOD;
      if (location.getAccuracy() &lt;= ACCEPTED_ACCURACY)
          return LocationQuality.ACCEPTED;
      return LocationQuality.BAD;
  }
  
  private synchronized void updateBestLocation(Location location) {
      bestLocation = getBestLocation(location, bestLocation);
  }
  
  // Pretty much an unmodified version of googles example
  protected Location getBestLocation(Location location,
          Location currentBestLocation) {
      if (currentBestLocation == null) {
          // A new location is always better than no location
          return location;
      }
      if (location == null) return currentBestLocation;
      // Check whether the new location fix is newer or older
      long timeDelta = location.getTime() - currentBestLocation.getTime();
      boolean isSignificantlyNewer = timeDelta &gt; TWO_MINUTES;
      boolean isSignificantlyOlder = timeDelta &lt; -TWO_MINUTES;
      boolean isNewer = timeDelta &gt; 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 location;
          // If the new location is more than two minutes older, it must be
          // worse
      } else if (isSignificantlyOlder) {
          return currentBestLocation;
      }
      // Check whether the new location fix is more or less accurate
      int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
              .getAccuracy());
      boolean isLessAccurate = accuracyDelta &gt; 0;
      boolean isMoreAccurate = accuracyDelta &lt; 0;
      boolean isSignificantlyLessAccurate = accuracyDelta &gt; 200;
      // Check if the old and new location are from the same provider
      boolean isFromSameProvider = isSameProvider(location.getProvider(),
              currentBestLocation.getProvider());
      // Determine location quality using a combination of timeliness and
      // accuracy
      if (isMoreAccurate) {
          return location;
      } else if (isNewer &amp;&amp; !isLessAccurate) {
          return location;
      } else if (isNewer &amp;&amp; !isSignificantlyLessAccurate
              &amp;&amp; isFromSameProvider) {
          return location;
      }
      return bestLocation;
  }
  
  /** Checks whether two providers are the same */
  private boolean isSameProvider(String provider1, String provider2) {
      if (provider1 == null) {
          return provider2 == null;
      }
      return provider1.equals(provider2);
  }
  </code></pre>