Good way of getting the user's location in Android

151,572

Solution 1

Looks like we're coding the same application ;-)
Here is my current implementation. I'm still in the beta testing phase of my GPS uploader app, so there might be many possible improvements. but it seems to work pretty well so far.

/**
 * try to get the 'best' location selected from all providers
 */
private Location getBestLocation() {
    Location gpslocation = getLocationByProvider(LocationManager.GPS_PROVIDER);
    Location networkLocation =
            getLocationByProvider(LocationManager.NETWORK_PROVIDER);
    // if we have only one location available, the choice is easy
    if (gpslocation == null) {
        Log.d(TAG, "No GPS Location available.");
        return networkLocation;
    }
    if (networkLocation == null) {
        Log.d(TAG, "No Network Location available");
        return gpslocation;
    }
    // a locationupdate is considered 'old' if its older than the configured
    // update interval. this means, we didn't get a
    // update from this provider since the last check
    long old = System.currentTimeMillis() - getGPSCheckMilliSecsFromPrefs();
    boolean gpsIsOld = (gpslocation.getTime() < old);
    boolean networkIsOld = (networkLocation.getTime() < old);
    // gps is current and available, gps is better than network
    if (!gpsIsOld) {
        Log.d(TAG, "Returning current GPS Location");
        return gpslocation;
    }
    // gps is old, we can't trust it. use network location
    if (!networkIsOld) {
        Log.d(TAG, "GPS is old, Network is current, returning network");
        return networkLocation;
    }
    // both are old return the newer of those two
    if (gpslocation.getTime() > networkLocation.getTime()) {
        Log.d(TAG, "Both are old, returning gps(newer)");
        return gpslocation;
    } else {
        Log.d(TAG, "Both are old, returning network(newer)");
        return networkLocation;
    }
}

/**
 * get the last known location from a specific provider (network/gps)
 */
private Location getLocationByProvider(String provider) {
    Location location = null;
    if (!isProviderSupported(provider)) {
        return null;
    }
    LocationManager locationManager = (LocationManager) getApplicationContext()
            .getSystemService(Context.LOCATION_SERVICE);
    try {
        if (locationManager.isProviderEnabled(provider)) {
            location = locationManager.getLastKnownLocation(provider);
        }
    } catch (IllegalArgumentException e) {
        Log.d(TAG, "Cannot acces Provider " + provider);
    }
    return location;
}

Edit: here is the part that requests the periodic updates from the location providers:

public void startRecording() {
    gpsTimer.cancel();
    gpsTimer = new Timer();
    long checkInterval = getGPSCheckMilliSecsFromPrefs();
    long minDistance = getMinDistanceFromPrefs();
    // receive updates
    LocationManager locationManager = (LocationManager) getApplicationContext()
            .getSystemService(Context.LOCATION_SERVICE);
    for (String s : locationManager.getAllProviders()) {
        locationManager.requestLocationUpdates(s, checkInterval,
                minDistance, new LocationListener() {

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

                    @Override
                    public void onProviderEnabled(String provider) {}

                    @Override
                    public void onProviderDisabled(String provider) {}

                    @Override
                    public void onLocationChanged(Location location) {
                        // if this is a gps location, we can use it
                        if (location.getProvider().equals(
                                LocationManager.GPS_PROVIDER)) {
                            doLocationUpdate(location, true);
                        }
                    }
                });
        // //Toast.makeText(this, "GPS Service STARTED",
        // Toast.LENGTH_LONG).show();
        gps_recorder_running = true;
    }
    // start the gps receiver thread
    gpsTimer.scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            Location location = getBestLocation();
            doLocationUpdate(location, false);
        }
    }, 0, checkInterval);
}

public void doLocationUpdate(Location l, boolean force) {
    long minDistance = getMinDistanceFromPrefs();
    Log.d(TAG, "update received:" + l);
    if (l == null) {
        Log.d(TAG, "Empty location");
        if (force)
            Toast.makeText(this, "Current location not available",
                    Toast.LENGTH_SHORT).show();
        return;
    }
    if (lastLocation != null) {
        float distance = l.distanceTo(lastLocation);
        Log.d(TAG, "Distance to last: " + distance);
        if (l.distanceTo(lastLocation) < minDistance && !force) {
            Log.d(TAG, "Position didn't change");
            return;
        }
        if (l.getAccuracy() >= lastLocation.getAccuracy()
                && l.distanceTo(lastLocation) < l.getAccuracy() && !force) {
            Log.d(TAG,
                    "Accuracy got worse and we are still "
                      + "within the accuracy range.. Not updating");
            return;
        }
        if (l.getTime() <= lastprovidertimestamp && !force) {
            Log.d(TAG, "Timestamp not never than last");
            return;
        }
    }
    // upload/store your location here
}

Things to consider:

  • do not request GPS updates too often, it drains battery power. I currently use 30 min as default for my application.

  • add a 'minimum distance to last known location' check. without this, your points will "jump around" when GPS is not available and the location is being triangulated from the cell towers. or you can check if the new location is outside of the accuracy value from the last known location.

Solution 2

To select the right location provider for your app, you can use Criteria objects:

Criteria myCriteria = new Criteria();
myCriteria.setAccuracy(Criteria.ACCURACY_HIGH);
myCriteria.setPowerRequirement(Criteria.POWER_LOW);
// let Android select the right location provider for you
String myProvider = locationManager.getBestProvider(myCriteria, true); 

// finally require updates at -at least- the desired rate
long minTimeMillis = 600000; // 600,000 milliseconds make 10 minutes
locationManager.requestLocationUpdates(myProvider,minTimeMillis,0,locationListener); 

Read the documentation for requestLocationUpdates for more details on how the arguments are taken into account:

The frequency of notification may be controlled using the minTime and minDistance parameters. If minTime is greater than 0, the LocationManager could potentially rest for minTime milliseconds between location updates to conserve power. If minDistance is greater than 0, a location will only be broadcasted if the device moves by minDistance meters. To obtain notifications as frequently as possible, set both parameters to 0.

More thoughts

  • You can monitor the accuracy of the Location objects with Location.getAccuracy(), which returns the estimated accuracy of the position in meters.
  • the Criteria.ACCURACY_HIGH criterion should give you errors below 100m, which is not as good as GPS can be, but matches your needs.
  • You also need to monitor the status of your location provider, and switch to another provider if it gets unavailable or disabled by the user.
  • The passive provider may also be a good match for this kind of application: the idea is to use location updates whenever they are requested by another app and broadcast systemwide.

Solution 3

Answering the first two points:

  • GPS will always give you a more precise location, if it is enabled and if there are no thick walls around.

  • If location did not change, then you can call getLastKnownLocation(String) and retrieve the location immediately.

Using an alternative approach:

You can try getting the cell id in use or all the neighboring cells

TelephonyManager mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
GsmCellLocation loc = (GsmCellLocation) mTelephonyManager.getCellLocation(); 
Log.d ("CID", Integer.toString(loc.getCid()));
Log.d ("LAC", Integer.toString(loc.getLac()));
// or 
List<NeighboringCellInfo> list = mTelephonyManager.getNeighboringCellInfo ();
for (NeighboringCellInfo cell : list) {
    Log.d ("CID", Integer.toString(cell.getCid()));
    Log.d ("LAC", Integer.toString(cell.getLac()));
}

You can refer then to cell location through several open databases (e.g., http://www.location-api.com/ or http://opencellid.org/ )


The strategy would be to read the list of tower IDs when reading the location. Then, in next query (10 minutes in your app), read them again. If at least some towers are the same, then it's safe to use getLastKnownLocation(String). If they're not, then wait for onLocationChanged(). This avoids the need of a third party database for the location. You can also try this approach.

Solution 4

This is my solution which works fairly well:

private Location bestLocation = null;
private Looper looper;
private boolean networkEnabled = false, gpsEnabled = false;

private synchronized void setLooper(Looper looper) {
    this.looper = looper;
}

private synchronized void stopLooper() {
    if (looper == null) return;
    looper.quit();
}

@Override
protected void runTask() {
    final LocationManager locationManager = (LocationManager) service
            .getSystemService(Context.LOCATION_SERVICE);
    final SharedPreferences prefs = getPreferences();
    final int maxPollingTime = Integer.parseInt(prefs.getString(
            POLLING_KEY, "0"));
    final int desiredAccuracy = Integer.parseInt(prefs.getString(
            DESIRED_KEY, "0"));
    final int acceptedAccuracy = Integer.parseInt(prefs.getString(
            ACCEPTED_KEY, "0"));
    final int maxAge = Integer.parseInt(prefs.getString(AGE_KEY, "0"));
    final String whichProvider = prefs.getString(PROVIDER_KEY, "any");
    final boolean canUseGps = whichProvider.equals("gps")
            || whichProvider.equals("any");
    final boolean canUseNetwork = whichProvider.equals("network")
            || whichProvider.equals("any");
    if (canUseNetwork)
        networkEnabled = locationManager
                .isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    if (canUseGps)
        gpsEnabled = locationManager
                .isProviderEnabled(LocationManager.GPS_PROVIDER);
    // If any provider is enabled now and we displayed a notification clear it.
    if (gpsEnabled || networkEnabled) removeErrorNotification();
    if (gpsEnabled)
        updateBestLocation(locationManager
                .getLastKnownLocation(LocationManager.GPS_PROVIDER));
    if (networkEnabled)
        updateBestLocation(locationManager
                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
    if (desiredAccuracy == 0
            || getLocationQuality(desiredAccuracy, acceptedAccuracy,
                    maxAge, bestLocation) != LocationQuality.GOOD) {
        // Define a listener that responds to location updates
        LocationListener locationListener = new LocationListener() {

            public void onLocationChanged(Location location) {
                updateBestLocation(location);
                if (desiredAccuracy != 0
                        && getLocationQuality(desiredAccuracy,
                                acceptedAccuracy, maxAge, bestLocation)
                                == LocationQuality.GOOD)
                    stopLooper();
            }

            public void onProviderEnabled(String provider) {
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER))networkEnabled =true;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER)) gpsEnabled = true;
                // The user has enabled a location, remove any error
                // notification
                if (canUseGps && gpsEnabled || canUseNetwork
                        && networkEnabled) removeErrorNotification();
            }

            public void onProviderDisabled(String provider) {
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER))networkEnabled=false;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER)) gpsEnabled = false;
                if (!gpsEnabled && !networkEnabled) {
                    showErrorNotification();
                    stopLooper();
                }
            }

            public void onStatusChanged(String provider, int status,
                    Bundle extras) {
                Log.i(LOG_TAG, "Provider " + provider + " statusChanged");
                if (isSameProvider(provider,
                        LocationManager.NETWORK_PROVIDER)) networkEnabled = 
                        status == LocationProvider.AVAILABLE
                        || status == LocationProvider.TEMPORARILY_UNAVAILABLE;
                else if (isSameProvider(provider,
                        LocationManager.GPS_PROVIDER))
                    gpsEnabled = status == LocationProvider.AVAILABLE
                      || status == LocationProvider.TEMPORARILY_UNAVAILABLE;
                // None of them are available, stop listening
                if (!networkEnabled && !gpsEnabled) {
                    showErrorNotification();
                    stopLooper();
                }
                // The user has enabled a location, remove any error
                // notification
                else if (canUseGps && gpsEnabled || canUseNetwork
                        && networkEnabled) removeErrorNotification();
            }
        };
        if (networkEnabled || gpsEnabled) {
            Looper.prepare();
            setLooper(Looper.myLooper());
            // Register the listener with the Location Manager to receive
            // location updates
            if (canUseGps)
                locationManager.requestLocationUpdates(
                        LocationManager.GPS_PROVIDER, 1000, 1,
                        locationListener, Looper.myLooper());
            if (canUseNetwork)
                locationManager.requestLocationUpdates(
                        LocationManager.NETWORK_PROVIDER, 1000, 1,
                        locationListener, Looper.myLooper());
            Timer t = new Timer();
            t.schedule(new TimerTask() {

                @Override
                public void run() {
                    stopLooper();
                }
            }, maxPollingTime * 1000);
            Looper.loop();
            t.cancel();
            setLooper(null);
            locationManager.removeUpdates(locationListener);
        } else // No provider is enabled, show a notification
        showErrorNotification();
    }
    if (getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
            bestLocation) != LocationQuality.BAD) {
        sendUpdate(new Event(EVENT_TYPE, locationToString(desiredAccuracy,
                acceptedAccuracy, maxAge, bestLocation)));
    } else Log.w(LOG_TAG, "LocationCollector failed to get a location");
}

private synchronized void showErrorNotification() {
    if (notifId != 0) return;
    ServiceHandler handler = service.getHandler();
    NotificationInfo ni = NotificationInfo.createSingleNotification(
            R.string.locationcollector_notif_ticker,
            R.string.locationcollector_notif_title,
            R.string.locationcollector_notif_text,
            android.R.drawable.stat_notify_error);
    Intent intent = new Intent(
            android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    ni.pendingIntent = PendingIntent.getActivity(service, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT);
    Message msg = handler.obtainMessage(ServiceHandler.SHOW_NOTIFICATION);
    msg.obj = ni;
    handler.sendMessage(msg);
    notifId = ni.id;
}

private void removeErrorNotification() {
    if (notifId == 0) return;
    ServiceHandler handler = service.getHandler();
    if (handler != null) {
        Message msg = handler.obtainMessage(
                ServiceHandler.CLEAR_NOTIFICATION, notifId, 0);
        handler.sendMessage(msg);
        notifId = 0;
    }
}

@Override
public void interrupt() {
    stopLooper();
    super.interrupt();
}

private String locationToString(int desiredAccuracy, int acceptedAccuracy,
        int maxAge, Location location) {
    StringBuilder sb = new StringBuilder();
    sb.append(String.format(
            "qual=%s time=%d prov=%s acc=%.1f lat=%f long=%f",
            getLocationQuality(desiredAccuracy, acceptedAccuracy, maxAge,
                    location), location.getTime() / 1000, // Millis to
                                                            // seconds
            location.getProvider(), location.getAccuracy(), location
                    .getLatitude(), location.getLongitude()));
    if (location.hasAltitude())
        sb.append(String.format(" alt=%.1f", location.getAltitude()));
    if (location.hasBearing())
        sb.append(String.format(" bearing=%.2f", location.getBearing()));
    return sb.toString();
}

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(int desiredAccuracy,
        int acceptedAccuracy, int maxAge, Location location) {
    if (location == null) return LocationQuality.BAD;
    if (!location.hasAccuracy()) return LocationQuality.BAD;
    long currentTime = System.currentTimeMillis();
    if (currentTime - location.getTime() < maxAge * 1000
            && location.getAccuracy() <= desiredAccuracy)
        return LocationQuality.GOOD;
    if (acceptedAccuracy == -1
            || location.getAccuracy() <= acceptedAccuracy)
        return LocationQuality.ACCEPTED;
    return LocationQuality.BAD;
}

private synchronized void updateBestLocation(Location location) {
    bestLocation = getBestLocation(location, bestLocation);
}

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 > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    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 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 > 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(),
            currentBestLocation.getProvider());
    // Determine location quality using a combination of timeliness and
    // accuracy
    if (isMoreAccurate) {
        return location;
    } else if (isNewer && !isLessAccurate) {
        return location;
    } else if (isNewer && !isSignificantlyLessAccurate
            && 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);
}

Solution 5

Location accuracy depends mostly on the location provider used:

  1. GPS - will get you several meters accuracy (assuming you have GPS reception)
  2. Wifi - Will get you few hundred meters accuracy
  3. Cell Network - Will get you very inaccurate results (I've seen up to 4km deviation...)

If it's accuracy you are looking for, then GPS is your only option.

I've read a very informative article about it here.

As for the GPS timeout - 60 seconds should be sufficient, and in most cases even too much. I think 30 seconds is OK and sometimes even less than 5 sec...

if you only need a single location, I'd suggest that in your onLocationChanged method, once you receive an update you'll unregister the listener and avoid unnecessary usage of the GPS.

Share:
151,572

Related videos on Youtube

Nicklas A.
Author by

Nicklas A.

Updated on August 13, 2020

Comments

  • Nicklas A.
    Nicklas A. almost 4 years

    The problem:

    Getting the user's current location within a threshold ASAP and at the same time conserve battery.

    Why the problem is a problem:

    First off, android has two providers; network and GPS. Sometimes network is better and sometimes the GPS is better.

    By "better" I mean speed vs. accuracy ratio.
    I'm willing to sacrifice a few meters in accuracy if I can get the location almost instant and without turning on the GPS.

    Secondly, if you request updates for location changes nothing is sent if the current location is stable.

    Google has an example of determining the "best" location here: http://developer.android.com/guide/topics/location/obtaining-user-location.html#BestEstimate
    But I think it's no where near as good as it should/could be.

    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.

    What I need help with:

    I need to find a good way to determine the "best" location, maybe though some heuristic or maybe through some 3rd party library.

    This does not mean determine the best provider!
    I'm probably gonna use all providers and picking the best of them.

    Background of the app:

    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.
    The app should conserve as much battery as possible and the location should have X (50-100?) meters accuracy.

    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.

    Misc:

    What do you think are reasonable values on desired and accepted accuracies?
    I've been using 100m as accepted and 30m as desired, is this to much to ask?
    I'd like to be able to plot the user's path on a map later.
    Is 100m for desired and 500m for accepted better?

    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?


    This is my current code, any feedback is appreciated (apart from the lack of error checking which is TODO):

    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() < MAX_AGE
                && location.getAccuracy() <= GOOD_ACCURACY)
            return LocationQuality.GOOD;
        if (location.getAccuracy() <= 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 > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        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 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 > 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(),
                currentBestLocation.getProvider());
        // Determine location quality using a combination of timeliness and
        // accuracy
        if (isMoreAccurate) {
            return location;
        } else if (isNewer && !isLessAccurate) {
            return location;
        } else if (isNewer && !isSignificantlyLessAccurate
                && 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);
    }
    
    • Matthew
      Matthew almost 11 years
      Chiming in really late, but the "Fused Location Provider" that was recently announced at IO 2013 looks like it addresses many of your needs -- developer.android.com/google/play-services/location.html
    • Gavriel
      Gavriel over 9 years
      shouldn't the last line of getBestLocation() be: return currentBestLocation; instead of return bestLocation;?
  • Nicklas A.
    Nicklas A. almost 13 years
    Yeah, but I the problem comes if the lastKnownLocation is really bad. I need a good way of deciding the best of two locations.
  • Aleadam
    Aleadam almost 13 years
    You can store the towers info and check if those towers changed. If they did, then wait for a new location, if not (or if only some changed), then reuse it. That way you avoid comparing the tower locations to a database.
  • Nicklas A.
    Nicklas A. almost 13 years
    Using towers seems like a big overkill to me, good idea though.
  • Aleadam
    Aleadam almost 13 years
    @Nicklas the code does not get any more complicated than that. You will need android.Manifest.permission#ACCESS_COARSE_UPDATES, though.
  • Nicklas A.
    Nicklas A. almost 13 years
    Yeah, but I still need to use a third party service and also I need a way to decide when to user the tower info over the location data, this just add an extra layer of complexity.
  • Nicklas A.
    Nicklas A. almost 13 years
    Ah, I see. Well, the location will probably be fetched every 10 minutes or so, then it seems kinda weird that you need to wait 10 minutes for a location
  • Aleadam
    Aleadam almost 13 years
    @Nicklas you don't. I based that time in what you said above. If you keep listening in the background, you can use requestLocationUpdates(...), to set up the time span you want (even 1 second), but that would force the phone to stay awake for that.
  • Nicklas A.
    Nicklas A. almost 13 years
    I don't really care from where I get my location, I don't want to limit me to one provider
  • Muzikant
    Muzikant almost 13 years
    You can register all location providers available on the device (you can get the list of all providers from LocationManager.getProviders()), but if you are looking for an accurate fix, in most cases the network provider will not be useful for you.
  • Nicklas A.
    Nicklas A. almost 13 years
    You never actually get a fresh location, you only use locations that happens to be there from previous updates. I think this code would benefit greatly by actually adding a listener that updates the location by turning on the GPS from time to time.
  • Nicklas A.
    Nicklas A. almost 13 years
    I've looked into Criteria but what if the latest network location is awesome (it might know through wifi) and it takes no time or battery to get it (getLastKnown), then criteria will probably disregard that and return the GPS instead. I can't believe google has made it this difficult for developers.
  • Nicklas A.
    Nicklas A. almost 13 years
    Yeah, but this does is not a question about choose between providers, this is a question about getting the best location generally (even when there is multiple providers involved)
  • Nicklas A.
    Nicklas A. almost 13 years
    Interesting indeed, they only seem to use WiFi however which is very nice but I still need it to work when there is no wifi or 3G/2G connection around so this would be adding another layer of abstraction. Good catch though.
  • Gryphius
    Gryphius almost 13 years
    sorry, I thought you were only interested in the part that selects the best from all available locations. I added the code above that requests these as well. if a new gps location is received, it is stored/uploaded right away. if I receive a network location update I store it for reference and 'hope' that I'll receive a gps update as well until the next location check happens.
  • Ed Burnette
    Ed Burnette almost 13 years
    Skyhook appears to use a combination of WiFi, GPS, and cell towers. See skyhookwireless.com/howitworks for the technical details. They have gotten several design wins lately, for example Mapquest, Twydroid, ShopSavvy, and the Sony NGP. Note that downloading and trying out their SDK appears to be free but you have to contact them about a license for distributing it in your app. Unfortunately they don't list the price on their web site.
  • Nicklas A.
    Nicklas A. almost 13 years
    Oh, I see. Well, if it isn't free to use commercially then I'm afraid I cannot use it.
  • Nicklas A.
    Nicklas A. almost 13 years
    Well, it isn't important that it's the very best, just that it's good enough to plot on a map and that I don't drain the battery since this is a background task.
  • Stéphane
    Stéphane almost 13 years
    On top of using the Criteria, you could, at each location update sent by the provider you selected, check the lastKnowLocation for the GPS provider and compare it (accuracy and date) to your current location. But this seems to me a nice-to-have rather than a requirement from your specifications ; if somewhat better accuracy is sometimes achieved, will it really be useful to your users?
  • Nicklas A.
    Nicklas A. almost 13 years
    That is what I'm doing now, the problem is I have a hard time figuring out if the last know is good enough. I can also add that I don't have to limit myself to one provider, the more I use the faster I might get a lock.
  • Nicklas A.
    Nicklas A. almost 13 years
    Even though this does not directly answer my question it is the most related answer, therefor I'm awarding you the bounty, thanks for your help!
  • Eduardo
    Eduardo about 12 years
    Have in mind that PASSIVE_PROVIDER requires API Level 8 or higher.
  • School Boy
    School Boy about 12 years
    Hi Nicklas i have same equirement so could i communicate to you by any means.. i would be thank full to you if you could helps us..
  • Diego
    Diego over 11 years
    @Gryphius, you have a startRecording() method, but how do I stop all the updates?
  • Gryphius
    Gryphius over 11 years
    I also had a stopRecording() method which cancelled the timer. I eventually switched from a timer to a ScheduledThreadPoolExecutor, so stopRecording now basically calls executor.shutdown() and de-registers all location update listeners
  • Diego
    Diego over 11 years
    I tried to cancel de gpsTimer.cancel(), but the listeners, updating. I think I need to call locationManager.removeUpdates, but which Listeners do I cancel, do you still have the stopRecording() method? Could you add that to your answer? Thanks a lot!
  • Gryphius
    Gryphius over 11 years
    according to my scm, stopRecording only called gpsTimer.cancel() and set gps_recorder_running=false, so like in your case, no cleanup of the listeners back then. the current code keeps track of all active listeners in a vector, I didn't have this when I wrote this answer 1.5 years ago.
  • Radu
    Radu about 11 years
    "and if there are no thick walls around." - even fiber glass can make GPS not fix a location...
  • Gaucho
    Gaucho about 11 years
    @Stéphane sorry for the edit. Don't take care of it. Your post is correct. I did that edit for error. Sorry. Regards.
  • Stéphane
    Stéphane about 11 years
    @Gaucho I actually don't see an edit, I guess you have reversed it... Anyway no worries, and thanks for the review.
  • M4rk
    M4rk about 10 years
    Could you post anywhere (github, pastebin), the whole class? Really appreciated
  • M4rk
    M4rk about 10 years
    Could you post the whole code? Thanks, really appreciated
  • Gryphius
    Gryphius about 10 years
    it is already on github, but I'm not sure this is still the best way to do GPS stuff nowadays. Afaik they have made many improvements to the location API since I wrote this code.
  • Nicklas A.
    Nicklas A. about 10 years
    That is all of the code. I don't have access to the project any more.
  • Firas Al Mannaa
    Firas Al Mannaa almost 10 years
    thanks for this great example ... one question, what is this method getGPSCheckMilliSecsFromPrefs();? @Gryphius
  • Gryphius
    Gryphius almost 10 years
    this returns the (user configurable) location check interval in milliseconds. smaller amount means requesting the location more often -> higher accuracy for gps tracks but faster battery drain
  • Nezam
    Nezam almost 10 years
    add a 'minimum distance to last known location' check. without this, your points will "jump around" :: can you tell us how to achieve this? this is exactly the problem i am facing right now.
  • Gryphius
    Gryphius almost 10 years
    it's in the code above. the minimum distance is a configuration setting : long minDistance = getMinDistanceFromPrefs(); , and then further down if (l.distanceTo(lastLocation) < minDistance...
  • Nezam
    Nezam almost 10 years
    when and how often should startRecording be called ? Basically i am plotting currentlocation in OnResume ?
  • Gödel77
    Gödel77 over 9 years
    You seem to have taken the code of this project "android-protips-location" and it's still alive. People can see how it works here code.google.com/p/android-protips-location/source/browse/tru‌​nk/…
  • S.M_Emamian
    S.M_Emamian about 9 years
    I think that using Place API is the best way. developers.google.com/places/training
  • jean d'arme
    jean d'arme about 9 years
    Would it work with this problem? stackoverflow.com/questions/30236136/…
  • Admin
    Admin over 8 years
    How I can get lat/lon ?
  • C0D3
    C0D3 over 8 years
    Where do you get the isProvidedSupported function? Sorry, I'm new to android
  • Gryphius
    Gryphius over 8 years
    @c0d3Junk13 you'll find this method in the github source I've linked in an earlier comment.
  • Narendra Singh
    Narendra Singh almost 8 years
    @Gryphius where is getGPSCheckMilliSecsFromPrefs?
  • dhami_ji
    dhami_ji over 6 years
    I am from future according to this question asked date , my question is does getBearing() will work with your code, bcoz i have seen many answers saying that network providers will give poor accuracy of getBearing().
  • Nouman Ch
    Nouman Ch over 6 years
    getGPSCheckMilliSecsFromPrefs() post this method plz