Location

Any real estate agent will tell you the three most important things are location, location, location. Android understands that and lets your application know where the device is. (It won't tell you if the device is in a good school district, although I am pretty sure there is an application for that.) Listing 7–8 shows how to request location updates using the system location services.

Listing 7–8. Receiving Location Updates

    private void requestLocationUpdates() {
        LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);

        List<String> providers = lm.getAllProviders();

        if (providers != null&& ! providers.isEmpty()) {
            LocationListener listener = new LocationListener() {

                @Override
                public void onLocationChanged(Location location) {
                    Log.i(TAG, location.toString());
                }

                @Override
                public void onProviderDisabled(String provider) {
                    Log.i(TAG, provider + " location provider disabled");
                }

                @Override
                public void onProviderEnabled(String provider) {
                    Log.i(TAG, provider + " location provider enabled");
                }

                @Override
                public void onStatusChanged(String provider, int status, Bundle extras)
{
                    Log.i(TAG, provider + " location provider status changed to " + status);
                }
            };

            for (String name : providers) {
                Log.i(TAG, "Requesting location updates on " + name);
                lm.requestLocationUpdates(name, 0, 0, listener);
            }
        }
    }

NOTE: Your application needs either the ACCESS_COARSE_LOCATION permission or the ACCESS_FINE_LOCATION permission to be able to retrieve the location information. The GPS location provider requires the ACCESS_FINE_LOCATION permission while the network provider requires either ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION.

This piece of code has serious flaws, but you should still run it, for example in your application's onCreate() method, in order to see the impact this code has on the battery life.

On a Galaxy Tab 10.1, one can observe the following:

  • Three location providers are available (network, gps, and passive).
  • GPS location updates are very frequent (one update per second).
  • Network location updates are not as frequent (one update every 45 seconds).

If you let your application with this piece of code run for a while, you should see the battery level going down faster than usual. This code's three main flaws are:

  • Location listeners are not unregistered.
  • Location updates are most likely too frequent.
  • Location updates are requested on multiple providers.

Luckily, all these flaws can easily be fixed. Ultimately though, how you need to use the location services depends on what your application needs to do so there is no one single solution for all applications, so what is considered a flaw in one application may be a feature in another. Also note that there is typically not one single solution for all users: you should consider the needs of your users as well and offer different settings in your application to cater for your end-users. For example, some users may be willing to sacrifice battery life to get more frequent location updates while others would rather limit the number of updates to make sure their device does not need to be charged during their lunch break.

Unregistering a Listener

In order to unregister a listener, you can simply call removeUpdates() as showed in Listing 7–9. It is usually good to do that in onPause() like for broadcast receivers but your application many need to do that elsewhere. Listening for location updates for a long period of time will consume a lot of power so your application should try to get the information it needs and then stop listening for updates. In some cases it can be a good idea to offer a way for the user to force a location fix.

Listing 7–9. Disabling Location Listener

    private void disableLocationListener(LocationListener listener) {
        LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
        lm.removeUpdates(listener);
    }

The frequency of the updates can be adjusted when calling requestLocationUpdates().

Frequency of Updates

Even though the LocationManager class defines five requestLocationUpdates() methods, all have two parameters in common: minTime and minDistance. The first one, minTime, specifies the time interval for notifications in milliseconds. This is used only as a hint for the system to save power and the actual time between updates may be greater (or less) than specified. Obviously, greater values will result in greater power savings. The second one, minDistance, specifies the minimum distance interval for notifications. Greater values can still result in power savings as fewer instructions would be executed if your application is not notified of all location updates. Listing 7–10 shows how to register for updates with one hour between updates and a 100-meter threshold.

Listing 7–10. Receiving Location Updates Not As Frequently

    private void requestLocationUpdates() {
        LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);

        List<String> providers = lm.getAllProviders();

        if (providers != null) {
            for (String name : providers) {
                LocationListener listener = new LocationListener() {
                    …
                };

                Log.i(TAG, "Requesting location updates on " + name);
                lm.requestLocationUpdates(name, DateUtils.HOUR_IN_MILLIS * 1, 100,
listener);
            }
        }
    }

Choosing the right interval is more art than science and once again depends on your application. A navigation application would typically require frequent updates, but if the same navigation application can be used by hikers, for example, updates could be less frequent. Moreover, you may have to trade accuracy for power in some cases. Give your users the choice and offer settings that can be adjusted to let them choose the best behavior for themselves.

Multiple Providers

As mentioned earlier, Android defines multiple location providers:

  • GPS (Global Positioning System using satellites)
  • Network (using Cell-ID and Wi-Fi locations)
  • Passive (since API level 8)

The GPS location provider (LocationManager.GPS_PROVIDER) is typically the most accurate one, with a horizontal accuracy of about 10 meters (11 yards). The network location provider is not as accurate as the GPS, and the accuracy will depend on how many locations the system can use to compute the device's location. For example, my own logs show an accuracy of 48 meters for locations coming from the network location provider, or the width of a football field.

While more accurate, GPS locations are expensive in terms of time and battery usage. Getting a “fix” from the GPS location provider requires locking the signals from multiple satellites, which can take from a few seconds in an open field to an infinite amount of time if the device is indoors and signal cannot be locked (like in a parking garage when your car's GPS tries to acquire satellites). For example, it took about 35 seconds to get the first location update after GPS was enabled while inside the house, but only 5 seconds when the same test was repeated outside. When using the network location provider, it took 5 seconds to get the first location update, inside and outside. An Assisted GPS (AGPS) would typically provide faster locations fixes, but actual times would depend on information already cached by the device and the network access.

NOTE: The Galaxy Tab 10.1 used for these measurements is Wi-Fi only. Faster location fixes would be achieved by the network provider if cell ids were also used.

Even though receiving updates from multiple providers was listed as a flow earlier, it may not always be one. As a matter of fact, you may want to receive updates from several providers for a while in order to get a more accurate location fix.

The passive location provider is the one that can preserve the battery the most. When your application uses the passive location provider, it says it is interested in location updates but won't be proactively requesting location fixes. In other words, your application will simply wait for other applications, services, or system components to request location updates and will be notified together with these other listeners. Listing 7–11 shows how to receive passive location updates. To test whether your application receives updates, open another application that makes use of the location services, such as Maps.

Listing 7–11. Receiving Passive Location Updates

    private void requestPassiveLocationUpdates() {
        LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
        LocationListener listener = new LocationListener() {

            @Override
            public void onLocationChanged(Location location) {
                Log.i(TAG, "[PASSIVE] " + location.toString());

                // let's say you only care about GPS location updates

                if (LocationManager.GPS_PROVIDER.equals(location.getProvider())) {

                    // if you care about accuracy, make sure you call hasAccuracy()
                    // (same comment for altitude and bearing)

                    if (location.hasAccuracy() && (location.getAccuracy() < 10.0f)) {
                        // do something here
                    }
                }
            }

            @Override
            public void onProviderDisabled(String provider) {
                Log.i(TAG, "[PASSIVE] " + provider + " location provider disabled");
            }

            @Override
            public void onProviderEnabled(String provider) {
                Log.i(TAG, "[PASSIVE] " + provider + " location provider enabled");
            }

            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {
                Log.i(TAG, "[PASSIVE] " + provider + " location provider status changed
to " + status);
            }
        };

        Log.i(TAG, "Requesting passive location updates");
        lm.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
            DateUtils.SECOND_IN_MILLIS * 30, 100, listener);
    }

If you use that code and disable or enable Wi-Fi or GPS, you will notice the passive listener does not get notified when a provider is disabled or enabled, or when a provider's status changes. While this is usually not important, this may force your application to use the other location providers should it really care about being notified of these changes.

A good trade-off is to register a listener with the network location provider, which uses less power than the GPS provider, while also registering a listener with the passive location provider in order to get possibly more accurate location information from the GPS.

NOTE: Your application will need the ACCESS_FINE_LOCATION to use the passive location provider even if it only receives location updates from the network provider. This may end up being a problem if you believe this may raise privacy concerns with your users. There is currently no way to receive passive updates only from the network location provider and only ask for the ACCESS_COARSE_LOCATION permission.

Filtering Providers

Since our focus is on battery life, your application may want to filter out location providers with a high power requirement if using a passive location provider is not an option. Listing 7–12 shows how you can get the power requirement of all the location providers.

Listing 7–12. Location Providers Power Requirement

    private static String powerRequirementCodeToString(int powerRequirement) {
        switch (powerRequirement) {
        case Criteria.POWER_LOW: return "Low";
        case Criteria.POWER_MEDIUM: return "Medium";
        case Criteria.POWER_HIGH: return "High";
        default: return String.format("Unknown (%d)", powerRequirement);
        }
    }

    private void showLocationProvidersPowerRequirement() {
        LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);

        List<String> providers = lm.getAllProviders();

        if (providers != null) {
            for (String name : providers) {
                LocationProvider provider = lm.getProvider(name);
                if (provider != null) {
                    int powerRequirement = provider.getPowerRequirement();
                    Log.i(TAG, name + " location provider power requirement: " +
                        powerRequirementCodeToString(powerRequirement));
                }
            }
        }
    }

NOTE: As one would expect, the power requirement of the passive location provider is unknown.

However, since your application may have very specific needs, it may be easier to first specify as precisely as possible what location provider you are looking for. For example, your application may want to use a location provider that reports speed information in addition to coordinates. Listing 7–13 shows how you can create a Criteria object and find out which location provider you should be using.

Listing 7–13. Using Criteria to Find Location Provider

    private LocationProvider getMyLocationProvider() {
        LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
        Criteria criteria = new Criteria();
        LocationProvider provider = null;

        // define your criteria here
        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
        criteria.setAltitudeRequired(true);
        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT); // API level 9
        criteria.setBearingRequired(false);
        criteria.setCostAllowed(true); // most likely you want the user to be able to
set that
        criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW); // API level 9
        criteria.setPowerRequirement(Criteria.POWER_LOW);
        criteria.setSpeedAccuracy(Criteria.ACCURACY_MEDIUM); // API level 9
        criteria.setSpeedRequired(false);
        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT); // API level 9

        List<String> names = lm.getProviders(criteria, false); // perfect matches only

        if ((names != null) && ! names.isEmpty()) {
            for (String name : names) {
                provider = lm.getProvider(name);
                Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " "+
provider);
            }
            provider = lm.getProvider(names.get(0));
        } else {
            Log.d(TAG, "Could not find perfect match for location provider");

            String name = lm.getBestProvider(criteria, false); // not necessarily
perfect match

            if (name != null) {
                provider = lm.getProvider(name);
                Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " " +
provider);
            }
        }

        return provider;
    }

LocationManager.getProviders() and LocationManager.getBestProvider() differ quite significantly. While getProviders() will only return perfect matches, getBestProvider() will first search for a perfect match but may return a location provider that does not meet the criteria if no provider was a perfect match. The criteria are loosened in the following sequence:

  • Power requirement
  • Accuracy
  • Bearing
  • Speed
  • Altitude

Since this order may not necessarily be the policy you want to adopt to find a location provider, you may have to develop your own algorithm to find the right provider for your needs. Also, the algorithm may depend on the current battery status: your application may not be willing to loosen the power requirement criterion if the battery is low.

Last Known Location

Before you decide to register a location listener with a location provider, you may first want to check if a location is already known (and was cached by the system). The LocationManager class defines the getLastKnownLocation() method, which returns the last known location for a given provider, or null if no known location exists. While this location may be out of date, it is often a good starting point since this location can be retrieved instantly and calling this method won’t start the provider. Even applications that register a location listener usually first retrieve the last known location in order to be more responsive as it typically takes a few seconds before any location update is received. Listing 7–14 shows how a last known location can be retrieved.

Listing 7–14. Last Known Location

    private Location getLastKnownLocation() {
        LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
        List<String> names = lm.getAllProviders();
        Location location = null;

        if (names != null) {
            for (String name : names) {
                if (! LocationManager.PASSIVE_PROVIDER.equals(name)) {

                    Location l = lm.getLastKnownLocation(name);

                    if ((l != null) && (location == null || l.getTime() >
location.getTime())) {
                        location = l;

                        /*
                         * Warning: GPS and network providers' clocks may be out of sync
so comparing the times
                         * may not be such a good idea... We may not get the most recent
location fix after all.
                         */
                    }
                }
           }
        }

        return location;
   }

While a GPS is sensitive to satellite signals, most Android devices come with other types of sensors that can make Android applications more interesting than what you would typically find on a traditional computer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.13.219