Pages

Sunday, 10 March 2013

A Note on Google Apps for Android

Lately we've been busy bees in Mountain View, as you can see from the recent release of Android 1.6 to the open-source tree, not to mention some devices we're working on with partners that we think you'll really like. Of course, the community isn't sitting around either, and we've been seeing some really cool and impressive things, such as the custom Android builds that are popular with many enthusiasts. Recently there's been some discussion about an exchange we had with the developer of one of those builds, and I've noticed some confusion around what is and isn't part of Android's open source code. I want to take a few moments to clear up some of those misconceptions, and explain how Google's apps for Android fit in.

Everyone knows that mobile is a big deal, but for a long time it was hard to be a mobile app developer. Competing interests and the slow pace of platform innovation made it hard to create innovative apps. For our part, Google offers a lot of services — such as Google Search, Google Maps, and so on — and we found delivering those services to users' phones to be a very frustrating experience. But we also found that we weren't alone, so we formed the Open Handset Alliance, a group of like-minded partners, and created Android to be the platform that we all wished we had. To encourage broad adoption, we arranged for Android to be open-source. Google also created and operates Android Market as a service for developers to distribute their apps to Android users. In other words, we created Android because the industry needed an injection of openness. Today, we're thrilled to see all the enthusiasm that developers, users, and others in the mobile industry have shown toward Android.

With a high-quality open platform in hand, we then returned to our goal of making our services available on users' phones. That's why we developed Android apps for many of our services like YouTube, Gmail, Google Voice, and so on. These apps are Google's way of benefiting from Android in the same way that any other developer can, but the apps are not part of the Android platform itself. We make some of these apps available to users of any Android-powered device via Android Market, and others are pre-installed on some phones through business deals. Either way, these apps aren't open source, and that's why they aren't included in the Android source code repository. Unauthorized distribution of this software harms us just like it would any other business, even if it's done with the best of intentions.

I hope that clears up some of the confusion around Google's apps for Android. We always love seeing novel uses of Android, including custom Android builds from developers who see a need. I look forward to seeing what comes next!

ADC 2 Updates

Since the announcement of Android Developer Challenge 2 in May, Android phones continue to be deployed in countries worldwide. Android phones are currently available in over 20 countries, with more on the way. As I've mentioned earlier, we'll be including real-world users of these phones to help review and score your submissions. It is important to remember that your apps will be reviewed by judges around the world on actual devices; thus, be sure to make it extremely easy for users/judges to access your apps with minimum setup.

Some of you have been asking for clarifications on what we mean by "open only to applications that have not been published". To be specific, applications that are available on Android Market before August 1, 2009 will not be eligible to participate in the contest. Users have already been providing comments for apps that are currently available on Android Market, so it wouldn't make sense for them to "judge" the same apps again in this contest. In addition, apps that include or that are based on open source projects are fully welcomed, as long as the application itself is not on Android Market until August 1, 2009.

If you want to find out more details about ADC 2, you can find everything at the ADC 2 page along with the Terms and Conditions. For the moment, the most important thing to know is that ADC 2 submissions will be due August 31. I can't wait to see what you all come up with this time.

Happy coding -- and good luck!

A Deep Dive Into Location

[This post is by Reto Meier, Tech Lead for Android Developer Relations, who wrote the book on Android App development. — Tim Bray]

I'm a big fan of location-based apps, but not their seemingly-inevitable startup latency.

Whether it's finding a place to eat or searching for the nearest Boris Bike, I find the delay while waiting for the GPS to get a fix, and then for the results list to populate, to be interminable. Once I’m in a venue and ready to get some tips, check-in, or review the food, I’m frequently thwarted by a lack of data connection.

Rather than shaking my fist at the sky, I’ve written an open-source reference app that incorporates all of the tips, tricks, and cheats I know to reduce the time between opening an app and seeing an up-to-date list of nearby venues - as well as providing a reasonable level of offline support — all while keeping the impact on battery life to a minimum.

Show Me the Code

You can check-out the Android Protips for Location open source project from Google Code. Don’t forget to read the Readme.txt for the steps required to make it compile and run successfully.

What Does it Actually Do?

It uses the Google Places API to implement the core functionality of apps that use location to provide a list of nearby points of interest, drill down into their details, and then check-in/rate/review them.

The code implements many of the best-practices I detailed in my Google I/O 2011 session, Android Protips: Advanced Topics for Expert Android Developers (video), including using Intents to receive location updates, using the Passive Location Provider, using and monitoring device state to vary refresh rates, toggling your manifest Receivers at runtime, and using the Cursor Loader.

The app targets Honeycomb but supports Android platforms from 1.6 and up.

Nothing would make me happier than for you to cut/copy/borrow / steal this code to build better location-based apps. If you do, I’d love it if you told me about it!

Now that you’ve got the code, let’s take a closer look at it

My top priority was freshness: Minimize the latency between opening the app and being able to check in to a desired location, while still minimizing the impact of the app on battery life.

Related requirements:

  • The current location has to be found as quickly as possible.

  • The list of venues should update when the location changes.

  • The list of nearby locations and their details must be available when we’re offline.

  • Check-ins must be possible while we’re offline.

  • Location data and other user data must be handled properly (see our prior blog post on best practices).

Freshness means never having to wait

You can significantly reduce the latency for getting your first location fix by retrieving the last known location from the Location Manager each time the app is resumed.

In this snippet taken from the GingerbreadLastLocationFinder, we iterate through each location provider on the device — including those that aren't currently available — to find the most timely and accurate last known location.

List matchingProviders = locationManager.getAllProviders();for (String provider: matchingProviders) { Location location = locationManager.getLastKnownLocation(provider); if (location != null) { float accuracy = location.getAccuracy(); long time = location.getTime();  if ((time > minTime && accuracy < bestAccuracy)) { bestResult = location; bestAccuracy = accuracy; bestTime = time; } else if (time < minTime &&  bestAccuracy == Float.MAX_VALUE && time > bestTime){ bestResult = location; bestTime = time; } }}

If there is one or more locations available from within the allowed latency, we return the most accurate one. If not, we simply return the most recent result.

In the latter case (where it’s determined that the last location update isn't recent enough) this newest result is still returned, but we also request a single location update using that fastest location provider available.

if (locationListener != null && (bestTime < maxTime || bestAccuracy > maxDistance)) {  IntentFilter locIntentFilter = new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION); context.registerReceiver(singleUpdateReceiver, locIntentFilter);  locationManager.requestSingleUpdate(criteria, singleUpatePI);}

Unfortunately we can’t specify “fastest” when using Criteria to choose a location provider, but in practice we know that coarser providers — particularly the network location provider — tend to return results faster than the more accurate options. In this case I’ve requested coarse accuracy and low power in order to select the Network Provider when it’s available.

Note also that this code snippet shows the GingerbreadLastLocationFinder which uses the requestSingleUpdate method to receive a one-shot location update. This wasn’t available prior to Gingerbread - check out the LegacyLastLocationFinder to see how I have implemented the same functionality for devices running earlier platform versions.

The singleUpdateReceiver passes the received update back to the calling class through a registered Location Listener.

protected BroadcastReceiver singleUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { context.unregisterReceiver(singleUpdateReceiver);  String key = LocationManager.KEY_LOCATION_CHANGED; Location location = (Location)intent.getExtras().get(key);  if (locationListener != null && location != null) locationListener.onLocationChanged(location);  locationManager.removeUpdates(singleUpatePI); }};

Use Intents to receive location updates

Having obtained the most accurate/timely estimate of our current location, we also want to receive location updates.

The PlacesConstants class includes a number of values that determine the frequency of location updates (and the associated server polling). Tweak them to ensure that updates occur exactly as often as required.

// The default search radius when searching for places nearby.public static int DEFAULT_RADIUS = 150;// The maximum distance the user should travel between location updates. public static int MAX_DISTANCE = DEFAULT_RADIUS/2;// The maximum time that should pass before the user gets a location update.public static long MAX_TIME = AlarmManager.INTERVAL_FIFTEEN_MINUTES; 

The next step is to request the location updates from the Location Manager. In this snippet taken from the GingerbreadLocationUpdateRequester we can pass the Criteria used to determine which Location Provider to request updates from directly into the requestLocationUpdates call.

public void requestLocationUpdates(long minTime, long minDistance,  Criteria criteria, PendingIntent pendingIntent) { locationManager.requestLocationUpdates(minTime, minDistance,  criteria, pendingIntent);}

Note that we're passing in a Pending Intent rather than a Location Listener.

Intent activeIntent = new Intent(this, LocationChangedReceiver.class);locationListenerPendingIntent =  PendingIntent.getBroadcast(this, 0, activeIntent, PendingIntent.FLAG_UPDATE_CURRENT);

I generally prefer this over using Location Listeners as it offers the flexibility of registering receivers in multiple Activities or Services, or directly in the manifest.

In this app, a new location means an updated list of nearby venues. This happens via a Service that makes a server query and updates the Content Provider that populates the place list.

Because the location change isn’t directly updating the UI, it makes sense to create and register the associated LocationChangedReceiver in the manifest rather than the main Activity.

The Location Changed Receiver extracts the location from each update and starts the PlaceUpdateService to refresh the database of nearby locations.

if (intent.hasExtra(locationKey)) { Location location = (Location)intent.getExtras().get(locationKey); Log.d(TAG, "Actively Updating place list"); Intent updateServiceIntent =  new Intent(context, PlacesConstants.SUPPORTS_ECLAIR ? EclairPlacesUpdateService.class : PlacesUpdateService.class); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_LOCATION, location); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_RADIUS, PlacesConstants.DEFAULT_RADIUS); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, true); context.startService(updateServiceIntent);}

Monitor inactive providers for a better option

The snippet from PlacesActivity below shows how to monitor two important conditions:

  • The Location Provider we are using being deactivated.

  • A better Location Provider becoming available.

In either case, we simply re-run the process used to determine the best available provider and request location updates.

// Register a receiver that listens for when the provider I'm using has been disabled. IntentFilter intentFilter = new IntentFilter(PlacesConstants.ACTIVE_LOCATION_UPDATE_PROVIDER_DISABLED);registerReceiver(locProviderDisabledReceiver, intentFilter);// Listen for a better provider becoming available.String bestProvider = locationManager.getBestProvider(criteria, false);String bestAvailableProvider = locationManager.getBestProvider(criteria, true);if (bestProvider != null && !bestProvider.equals(bestAvailableProvider)) locationManager.requestLocationUpdates(bestProvider, 0, 0,  bestInactiveLocationProviderListener, getMainLooper());

Freshness means always being up to date. What if we could reduce startup latency to zero?

You can start the PlacesUpdateService in the background to refresh the list of nearby locations while your app is in the background. Done correctly, a relevant list of venues can be immediately available when you open the app.

Done poorly, your users will never find this out as you’ll have drained their battery too quickly.

Requesting location updates (particularly using the GPS) while your app isn’t in the foreground is poor practice, as it can significantly impact battery life. Instead, you can use the Passive Location Provider to receive location updates alongside other apps that have already requested them.

This extract from the FroyoLocationUpdateRequester enables passive updates on Froyo+ platforms.

public void requestPassiveLocationUpdates(long minTime, long minDistance, PendingIntent pendingIntent) { locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, PlacesConstants.MAX_TIME, PlacesConstants.MAX_DISTANCE, pendingIntent); }

As a result receiving background location updates is effectively free! Unfortunately the battery cost of your server downloads aren’t, so you’ll still need to carefully balance how often you act on passive location updates with battery life.

You can achieve a similar effect in pre-Froyo devices using inexact repeating non-wake alarms as shown in the LegacyLocationUpdateRequester.

public void requestPassiveLocationUpdates(long minTime, long minDistance,  PendingIntent pendingIntent) { alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,  System.currentTimeMillis()+PlacesConstants.MAX_TIME,  PlacesConstants.MAX_TIME, pendingIntent); }

Rather than receiving updates from the Location Manager, this technique manually checks the last known location at a frequency determined by the maximum location update latency.

This legacy technique is significantly less efficient, so you may choose to simply disable background updates on pre-Froyo devices.

We handle updates themselves within the PassiveLocationChangedReceiver which determines the current location and starts the PlaceUpdateService.

if (location != null) { Intent updateServiceIntent =  new Intent(context, PlacesConstants.SUPPORTS_ECLAIR ? EclairPlacesUpdateService.class : PlacesUpdateService.class);  updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_LOCATION, location); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_RADIUS,  PlacesConstants.DEFAULT_RADIUS); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, false); context.startService(updateServiceIntent); }

Using Intents to passively receive location updates when your app isn't active

You’ll note that we registered the Passive Location Changed Receiver in the application manifest.

As a result we can continue to receive these background updates even when the application has been killed by the system to free resources.

This offers the significant advantage of allowing the system to reclaim the resources used by your app, while still retaining the advantages of a zero latency startup.

If your app recognizes the concept of “exiting” (typically when the user clicks the back button on your home screen), it’s good form to turn off passive location updates - including disabling your passive manifest Receiver.

Being fresh means working offline

To add offline support we start by caching all our lookup results to the PlacesContentProvider and PlaceDetailsContentProvider.

Under certain circumstances we will also pre-fetch location details. This snippet from the PlacesUpdateService shows how pre-fetching is enabled for a limited number of locations.

Note that pre-fetching is also potentially disabled while on mobile data networks or when the battery is low.

if ((prefetchCount < PlacesConstants.PREFETCH_LIMIT) && (!PlacesConstants.PREFETCH_ON_WIFI_ONLY || !mobileData) && (!PlacesConstants.DISABLE_PREFETCH_ON_LOW_BATTERY || !lowBattery)) { prefetchCount++;  // Start the PlaceDetailsUpdateService to prefetch the details for this place.}

We use a similar technique to provide support for offline checkins. The PlaceCheckinService queues failed checkins, and checkins attempted while offline, to be retried (in order) when the ConnectivityChangedReceiver determines that we’re back online.

Optimizing battery life: Smart Services and using device state to toggle your manifest Receivers

There's no point running update services when we aren’t online, so the PlaceUpdateService checks for connectivity before attempting an update.

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();

If we’re not connected, the Passive and Active Location Changed Receivers are disabled and the the ConnectivityChangedReceiver is turned on.

ComponentName connectivityReceiver =  new ComponentName(this, ConnectivityChangedReceiver.class);ComponentName locationReceiver =  new ComponentName(this, LocationChangedReceiver.class);ComponentName passiveLocationReceiver =  new ComponentName(this, PassiveLocationChangedReceiver.class);pm.setComponentEnabledSetting(connectivityReceiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,  PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(locationReceiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,  PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(passiveLocationReceiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,  PackageManager.DONT_KILL_APP);

The ConnectivityChangedReceiver listens for connectivity changes. When a new connection is made, it simply disables itself and re-enables the location listeners.

Monitoring battery state to reduce functionality and save power

When your phone is on its last 15%, most apps are firmly in the back seat to conserving what watts you have remaining. We can register manifest Receivers to be alerted when the device enters or leaves the low battery state.

    

This snippet from the PowerStateChangedReceiver disables the PassiveLocationChangedReceiver whenever the device enters a low battery state, and turns it back on once the battery level is okay.

boolean batteryLow = intent.getAction().equals(Intent.ACTION_BATTERY_LOW); pm.setComponentEnabledSetting(passiveLocationReceiver, batteryLow ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,  PackageManager.DONT_KILL_APP);

You can extend this logic to disable all prefetching or reduce the frequency of your updates during low battery conditions.

What’s Next?

This is already a monster post, so I’m going to leave it there. I’ll follow up in the next week with a post on my personal blog, The Radioactive Yak, that will go in to more detail on the psychic and smooth elements of this app like using the Backup Manager and the Cursor Loader.

I also plan to build a similar reference app for news apps, so that I can spend more time reading and less time waiting.

In the mean time, happy coding!

ADC 2 Round 1 Scoring Complete

ADC 2 icon

The response to round one of the Android Developer Challenge 2 has been phenomenal! We originally expected that it would take two weeks to get all the necessary data to complete scoring. Over the last 10 days, more than 26,000 Android users reviewed and submitted our target of over 100 scores per application. With this enthusiastic support of the Android community, we are closing the first round of ADC 2 judging today.

We will now be reviewing the results and preparing for round 2. Please stay tuned for information about round 2, where the community, combined with a panel of judges, will narrow down the top 20 applications in each category to determine the final winners. Until then, users with the ADC 2 judging application currently installed will get a notice saying that round 1 is over. When round 2 opens, the judging application will resume giving out new submissions to score. We look forward to seeing the results of the final round and hope that you choose to help us score these top apps as well!