Showing posts with label Location. Show all posts
Showing posts with label Location. Show all posts

Monday, 2 March 2015

Google Play services 7.0 - Places Everyone!

Posted by Ian Lake, Developer Advocate



Today, we’re bringing you new tools to build better apps with the completion of the rollout of Google Play services 7.0. With this release, we’re delivering improvements to location settings experiences, a brand new API for place information, new fitness data, Google Play Games, and more.



Location Settings Dialog


While the FusedLocationProviderApi combines multiple sensors to give you the optimal location, the accuracy of the location your app receives still depends greatly on what settings are enabled on the device (e.g. GPS, wifi, airplane mode, etc). In Google Play services 7.0, we’re introducing a standard mechanism to check that the necessary location settings are enabled for a given LocationRequest to succeed. If there are possible improvements, you can display a one touch control for the user to change their settings without leaving your app.





This API provides a great opportunity to make for a much better user experience, particularly if location information is critical to the user experience of your app such as was the case with Google Maps when they integrated the Location Settings dialog and saw a dramatic increase in the number of users in a good location state.



Places API


Location can be so much more than a latitude and longitude: the new Places API makes it easy to get details from Google’s database of places and businesses. The built-in place picker makes it easy for the user to pick their current place and provides all the relevant place details including name, address, phone number, website, and more.





If you prefer to provide your own UI, the getCurrentPlace() API returns places directly around the user’s current location. Autocomplete predictions are also provided to allow a low latency search experience directly within your app.



You can also manually add places with the addPlace() API and report that the user is at a particular place, ensuring that even the most explorative users can input and share their favorite new places.






The Places API will also be available cross-platform: in a few days, you’ll be able to apply for the Places API for iOS beta program to ensure a great and consistent user experience across mobile platforms.



Google Fit


Google Fit makes building fitness apps easier with fitness specific APIs on retrieving sensor data like current location and speed, collecting and storing activity data in Google Fit’s open platform, and automatically aggregating that data into a single view of the user’s fitness data.



In Google Play services 7.0, the previous Fitness.API that you passed into your GoogleApiClient has now been replaced with a number of APIs, matching the high level set of Google Fit Android APIs:


  • SENSORS_API to access raw sensor data via SensorsApi

  • RECORDING_API to record data via RecordingApi

  • HISTORY_API for inserting, deleting, or reading data via HistoryApi

  • SESSIONS_API for managing sessions via SessionsApi

  • BLE_API to interact with Bluetooth Low Energy devices via BleApi

  • CONFIG_API to access custom data types and settings for Google Fit via ConfigApi



This change significantly reduces the memory requirement for Google Fit enabled apps running in the background. Like always, apps built on previous versions of Google Play services will continue to work, but we strongly suggest you rebuild your Google Fit enabled apps to take advantage of this change.



Having all the data can be an empowering part of making meaningful changes and Google Fit is augmenting their existing data types with the addition of body fat percentage and sleep data.





Google Play Games


Announced at Game Developers Conference (GDC), we’re offering new tools to supercharge your games on Google Play. Included in Google Play services 7.0 is the Nearby Connections API, allowing games to seamlessly connect smartphones and tablets as second-screen controls to the game running on your TV.





App Indexing


App Indexing lets Google index apps just like websites, enabling Google search results to deep-link directly into your native app. We've simplified the App Indexing API to make this integration even easier for you by combining the existing view()/viewEnd() and action()/end() flows into a single start() and end() API.



Changes to GoogleApiClient


GoogleApiClient serves as the common entry point for accessing Google APIs. For this release, we’ve made retrieval of Google OAuth 2.0 tokens part of GoogleApiClient, making it much easier to request server auth codes to access Google APIs.



SDK Now Available!


You can get started developing today by downloading the Google Play services SDK from the Android SDK Manager.



To learn more about Google Play services and the APIs available to you through it, visit the Google Services section on the Android Developer site.





Thursday, 16 May 2013

Social Gaming, Location, and More in Google Play Services

Posted by Greg Hartrell, Angana Ghosh, Francesco Nerieri, Francis Ma, and the Google Play services team


Some of the most exciting Android announcements at Google I/O this year are part of our latest Google Play services release, version 3.1.



The new version brings you Google Play games services, part of a new cloud-integrated platform for social gaming based on Google+ identity. Also included are location-based services that make it easier to build efficient location-aware apps. For apps using the popular Google Cloud Messaging platform, you can now take advantage of XMPP messaging and easier setup. Finally, Cross-Platform Single Sign On for Google+ Sign-In is now available to your apps.



You can get started using these APIs and services right away—Google Play services 3.1 is already rolling out to Android devices across the world, with support reaching all the way back to Froyo.



Google Play games services






Games are always popular with Android developers, and the announcement of Google Play game services raised the volume even more.



Google Play games services lets you make your games more social, with achievements, leaderboards, and multiplayer, and they help you extend your user’s games across multiple devices by storing game saves and settings in the cloud.



Several great Android games are already using these new game services, including World of Goo, Super Stickman Golf 2, Beach Buggy Blitz, Kingdom Rush, Eternity Warriors 2, and Osmos.



You can take advantage of the new services right away using the games services SDK included in Google Play services. For all the details, check out the Google Play games services documentation.






Location APIs



If you build location-aware Android apps, you’ll want to check out the new location APIs. They make it easy to build accurate, fast, and efficient apps, with new contextual features.




The Fused Location Provider intelligently manages the underlying location technology and gives you the best location according to your needs. We’ve simplified the location APIs and completely rewritten our location algorithm to make location more accurate, flexible and use less battery.



Using the new geofencing API, your app can set up geographic boundaries around specific locations and then receive notifications when the user enters or leaves those areas.



With apps becoming increasingly contextual, understanding what the user is doing is critical to surfacing the right content. A new activity recognition API makes it easy to check the the user’s current activity — still, walking, cycling, and in-vehicle — with very efficient use of the battery. We use low-power sensors and machine-learning classifiers to recognize the activity, giving you both both high accuracy and low battery usage.



To learn more, head over to our training classes at Making Your App Location Aware or dive directly into the reference docs.



Google Cloud Messaging



We’ve added APIs to make it easier to set up GCM in your apps, and in the service itself we’ve added new messaging capabilities for your apps to use.



A new registration API lets your app register with the service using a single method call and begin receiving messages as soon as the call returns.





In the GCM service itself we’ve added support for messaging over XMPP with the new GCM Cloud Connection Server (CCS). Your servers now have a persistent connection over which to send large numbers of messages, very quickly, and with no overhead. New APIs in Google Play services let apps send messages back upstream to third-party servers using CCS, without needing to manage network connections. This helps keep battery and data usage to a minimum.



Also new in the GCM service is a User Notifications API. This new API lets you synchronize notifications across a user’s multiple devices — when the user dismisses a notification on one device, the notification disappears automatically from all the other devices. To get started with GCM, head over to the developer documentation.




Google+ Cross-Platform Single Sign On



Many people use apps on multiple devices throughout the day, switching between their laptops, tablets, and mobile devices. After signing-in to an app on one device, it’s natural that when they pick up a different device and use the same app, they would expect to be signed in there as well.



To help you provide this kind of seamless transition between platforms and stay connected with users across devices, we’re adding Cross-Platform Single Sign On to our Google+ Sign-In capabilities.



If your app is already using Google+ Sign-In, you’ve already got support for Cross-Platform Single Sign On. This feature will be enabled automatically over the coming days.



Cross-Platform Single Sign On gives you a great way to build longer-running, cross-platform user experiences, and it dovetails perfectly with the new Google Play games services for bridging game state across devices using the cloud.



To learn more about Google+ Sign-In, check out http://developers.google.com/+.



More About Google Play Services



Google Play Services is our platform for offering you better integration with Google products, and providing new capabilities to use within your apps. To learn more about Google Play services and the APIs available to you through it, visit the Google Services area of the Android Developers site.



Thursday, 23 June 2011

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<String> 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.

<receiver android:name=".receivers.LocationChangedReceiver"/>

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.

<receiver android:name=".receivers.PassiveLocationChangedReceiver"/>

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.

<receiver android:name=".receivers.PowerStateChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
<action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
</intent-filter>
</receiver>

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!

Thursday, 9 September 2010

One Screen Turn Deserves Another

[This post is by Dan Morrill, Open Source & Compatibility Program Manager. — Tim Bray]

Android has an API for accessing a variety of sensor types, such as an accelerometer or light sensor. Two of the most commonly-used sensors are accelerometers and magnetometers (that is, compasses.) Applications and devices frequently use these as forms of user input, and to determine which way to orient the screen.

However, there’s a new wrinkle: recently, a few devices have shipped (see here and here) that run Android on screens that are naturally landscape in their orientation. That is, when held in the default position, the screens are wider than they are tall. This introduces a few fairly subtle issues that we’ve noticed causing problems in some apps. Now, part of the reason for this is that the Android SDK docs on the sensor API left a couple things unsaid, leading many developers to use them incorrectly. Even a couple of our own samples did the wrong thing. Sorry about that!

Fortunately, using these APIs correctly is pretty simple, if you keep three rules in mind:

  • The sensor coordinate system used by the API for the natural orientation of the device does not change as the device moves, and is the same as the OpenGL coordinate system.


  • Applications must not assume that the natural orientation is portrait. That's not true on all devices.


  • Applications that match sensor data to on-screen display must always use android.view.Display.getRotation() to map sensor coordinates to screen coordinates — even if their manifest specifies portrait-only display.


If you have a strong background in math, the three rules above may be all you need to work out the rest. But if that’s not you, the rest of this post explains things step-by-step, and gives some tips for using sensors correctly.

The Basic Problem

Before we dive in, here’s a tip that I personally have found to be helpful: always remember that the sensor data’s coordinate system never changes. Ever. The rest of this post is going to talk about coordinate systems and rotations and so on. But sometimes when your head is deep in 3D transforms, you can get disoriented, so I’ve found it helps to frequently remind myself that no matter what is happening to the screen, the sensor coordinate system never changes.

Now with that tip in mind, we need an example to talk about. Let’s consider a simple app that draws an arrow that always points in the direction of gravity, animating the arrow as the user moves the phone around, like a plumb-bob. When a typical phone is held normally, the arrow points down, as shown in Figure A:

(Note: In the figures in this post, the letter “G” means the direction of gravity in the sensor coordinate system. In Figure A, for example, “G = -y” means that gravity is aligned with the device’s negative-Y axis, as measured by the accelerometer. And remember — the sensor coordinate system never changes!)

This app is pretty straightforward to implement in OpenGL: you simply need to draw an arrow on a GL SurfaceView, after rotating the coordinate space in response to the sensor data returned by the accelerometer. This “just works” because — in this basic case — the OpenGL screen coordinate system lines up with the sensor coordinate system.

So, this technique works, and the arrow will always point down — until you turn the phone too far.

So What’s the Problem?

Most Android devices use the accelerometer to detect when the device is being held sideways, and rotate the screen accordingly. This normally causes the apps to display horizontally, from the point of view of the user.

What this reorientation actually does is remap the X and Y axes, causing the app to draw itself horizontally. However, the Android sensor APIs define the sensor coordinate space to be relative to the top and side of the device — not the short and long sides. When the system reorients the screen in response to holding the phone sideways, the sensor coordinate system no longer lines up with the screen’s coordinate system, and you get unexpected rotations in the display of your app. Figure B shows an example:

There are are a couple different fixes for this problem that are commonly used today, but we’ve noticed that these often don’t work properly on landscape-default devices.

A common first attempt to solve the auto-rotation problem is to simply lock the screen to portrait mode, via the android:screenOrientation attribute in AndroidManifest.xml. This prevents the system from performing a screen coordinate system remap in response to device orientation, and so the sensor and screen coordinate systems remain in sync. However, locking the screen to portrait mode this way prevents the coordinate systems from getting out of sync on portrait-default devices, but causes them to become out of sync on landscape-default devices. This is because it forces a screen reorientation on those devices.

The second common technique is to detect when the device is in landscape mode, and compensate for it by adding a rotation to the graphics that are displayed. Unfortunately, this technique is often only a partial fix, because if you aren’t careful about detecting landscape mode, you will again cause an unnecessary compensation on landscape-default devices.

The Correct Fix

So what’s a poor developer to do? This seems like a catch-22: you can’t prevent screen reorientation, but you can’t compensate for it, either.

Or can you? Actually, you can compensate — you just have to make sure you’re correctly detecting when compensation is necessary. The question is, how does the device tell you that it’s been reoriented? And the answer is: android.view.Display.getRotation().

That method will return one of four values, indicating that either the device has not been reoriented (ROTATION_0), or that it has been reoriented by 90 degrees, 180 degrees, or 270 degrees (which respectively are ROTATION_90, ROTATION_180, and ROTATION_270.)

Pay special attention to those last two. ROTATION_180 and ROTATION_270 mean that each device actually has two portrait and two landscape modes: normal portrait and landscape, and the upside-down versions of each. Some Android devices that do “360 reorientation” will use these rotation modes as well, so you need to handle this generally, beyond just accounting for portrait or landscape mode.

Once you have the screen orientation info in hand, you can treat it as a rotation around the screen’s Z axis when rendering graphics. By applying the rotation to the values you get from your SensorEventListener, you can correctly and reliably compensate for screen reorientations on all devices.

Note that Display.getRotation() will tell you if the screen has been reoriented at all, not that it was reoriented specifically in response to the accelerometer. For example, even if you disable accelerometer-based reorientation by using android:screenOrientation=”nosensor", your app might still be reoriented if the user has opened a hard keyboard on the device.

Because handling all this involves some math that can be a bit of a chore, as a convenience we’ve provided the android.hardware.sensor.SensorManager.remapCoordinateSystem() method to do much of this remapping work for you. If you choose not to do use this method, you can achieve a similar effect by essentially swapping axes, along with the rule of thumb that 2 axis swaps requires that you negate the third axis. (Since this is a bit error-prone, we do recommend that you use remapCoordinateSystem() when you can.)

Recipes for Sensuous Delights

Okay, now we’ve got a technique that we can rely on to work on all devices. But how do you update your app? To give you a more explicit helping hand on how to fix your apps, I’ve whipped up a few recipes for updating your apps.

Apps That Never Draw Sensor Data

Apps that never display graphics derived from sensor data usually don’t need to make any changes. Examples of this type of app are those that detect for bumps to the device, those that use sensors for gesture input, apps that monitor g-forces (watching for free-fall or acceleration), and so on. These apps aren’t drawing images that vary according to the device’s orientation.

This isn’t a hard and fast rule; there probably are some apps out there that do need to take screen orientation into consideration, even though they don’t draw graphics depicting the sensor data. But, if your app just uses sensors in the background, there’s a good chance you won’t need to make any changes.

Apps That Work in Both Portrait and Landscape

Most Android apps work fine in both portrait and landscape, using the standard tools. If your app is one of these and you also use sensors, the only change your app probably requires is a tweak to use the behavior I outlined above. That is:

  • Don’t assume that portrait is the default mode.


  • Don’t assume that locking your app to portrait mode solves this issue.


  • Don’t assume that disabling sensor-based reorientation solves this issue (since reorientations also occur on some devices when the user opens a keyboard.)


  • Check for the current device orientation via getRotation(), and compensate accordingly, as detailed earlier.


Apps That Only Work in One Orientation

Some apps — notably, many games — only work well (or at all!) in either portrait or landscape mode. It’s perfectly okay, of course, for such apps to lock themselves to the appropriate mode, and doing so simplifies the sensors quite a bit.

However, because Android devices actually support two landscape and two portrait modes, these apps still need to check the current orientation. That is, if an app locks itself to landscape mode, it will need to perform a compensation on portrait-default devices, but not on landscape-default devices. And of course — are you sick of hearing this yet? — this can be accomplished by checking the result of getRotation().

Phew! Quite a mouthful for what is a fairly straightforward notion, once you understand what’s going on. But if I had to distill all that down into a single sentence, it would be this: android.view.Display.getRotation() is your friend.

I hope you’ve found this information useful; what’s more, I hope you’ve found it practical. We’ll keep improving our SDK and docs, and I hope you’ll keep improving your apps.

Happy coding!

Followers