- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Email to a Friend
- Printer Friendly Page
- Report Inappropriate Content
They say you shouldn't play favorites, but I just can't help having a collection of favorite bugs. I was reminded of this on vacation in Hawaii last week (see avatar, above left). I was idly web surfing, when I looked around and found this large gecko observing me closely from the wall.
A gecko appreciates bugs on a whole new level.
Now there's a guy that really appreciates bugs! This gecko has his favorite bugs, and all of them have 6 legs. I have my favorite bugs, and all of mine make me "unscrew the unscrutable", so to speak. In this post, I describe a bug that will knock your socks off (all 6 legs). The bug was brought to my attention by MOTODEV member and developer Noisygecko, who works on Android software at Georgia Tech Research Institute in Atlanta, GA. The bug manifested as a problem with a preference setting for screen orientation.
App Preferences
Here's a brief summary of Preferences in Android. Preferences are user customizations, like font size, or audio volume, or landscape/portrait screen preference. Preferences (user choices) are saved across different runs of an app. To provide consistency across all apps, there are some framework classes in the android.preference package to store/edit/retrieve preferences.
You can retrieve a preference with a call providing the string name of the preference, and the default value to use (if this preference has not been set yet). A preference can be one of several atomic types, like float, boolean, int, and also String.
Here's how you retrieve a preference:
You get a SharedPreferences object with a static call. Then
invoke one of several get*() methods on the shared preference, supplying
the string name of the preference you want. You also provide the
default value to return, if this preference hasn't been set yet, like
this:
import android.preference.PreferenceManager.*
// ...
SharedPreferences pref =
SharedPreferences.getDefaultSharedPreferences( getContext() );
int orientPref = pref.getInt("orientation", DEFAULT_VALUE_CONST );
Here's how you set some preference value:
You get a SharedPreferences object, and use that to get a preference editor object, and use that to save a string key/value pair. After putting one or more preference values, you commit() the change, and it's written to the app's preference file.
int orientPref = SOME_VALUE; SharedPreferences pref =
SharedPreferences.getDefaultSharedPreferences ( getContext() );
SharedPreferences.Editor prefEditor = pref.edit(); prefEditor.putInt("orientation", orientPref); prefEditor.commit();
You can add a SharedPreferenceChangeListener so that you get an event when someone else changes a preference that affects your app. When your app has many settings, you can easily create a screen that supports editing them all. Do this by extending PreferenceActivity. Finally, there is support in the android.preference.PreferenceManager class for loading preferences from an XML file. There's a great tutorial on Android preferences here.
What was wanted to happen
This particular app wanted the screen orientation to be managed as a preference. There was an options menu item that let the user toggle between landscape/portrait screen orientation preference. Since the app only had one preference, there was no need to break this out into a separate PreferenceActivity. The event handler for the options menu item did two things:
- It set the orientation preference (as shown above), so the value would be remembered for the next run.
- It called a method in the Activity, to change to the requested screen orientation. That method is
void setRequestedOrientation(int requestedOrientation)
The argument to setRequestedOrientation is an int. You'll use a constant value, defined elsewhere in the Android framework, to express either "LANDSCAPE" or "PORTRAIT". Hmm, where is that constant defined? Screen orientation is part of the Configuration object used by the Alternative Resources framework, so let's take a look at class android.content.res.Configuration. Yes, that seems to have the constants we need:
code sample from android.content.res.Configuration
package android.content.res; public class Configuration { public static final int ORIENTATION_UNDEFINED = 0; public static final int ORIENTATION_PORTRAIT = 1; public static final int ORIENTATION_LANDSCAPE = 2;
so, in the event handler for the menu item that lets the user select landscape screen orientation, we should do
setRequestedOrientation(Configuration.ORIENTATION_LANDSCAPE);
No compile time errors, so proceed to testing.
What actually happened
Everything worked fine when the preference was set to portrait mode. A problem happened when the user set the orientation preference to landscape. Then the layout would flip back and forth between landscape and portrait orientations.
The bug analysis
<activity ... android:configChanges="orientation">
code sample from android.content.pm.ActivityInfo
package android.content.pm; public class ActivityInfo { public static final int SCREEN_ORIENTATION_UNSPECIFIED = -1; public static final int SCREEN_ORIENTATION_LANDSCAPE = 0; public static final int SCREEN_ORIENTATION_PORTRAIT = 1; public static final int SCREEN_ORIENTATION_USER = 2;With these very similar int constants in two different Android classes, the portrait value happens to have the same value in both classes. But the landscape value does not! And that's why the portrait case was working, and the landscape case was not.
The bug fix
The bug fix was a one liner. Change the event handler, for the menu item that lets the user select "landscape screen orientation", to
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
(Yes, and change the portrait orientation constant too, to come from the right class). Everything now worked perfectly. It's amazing that I haven't written this bug, or one just like it, into my own code before!
How the API should have been designed
public class ActivityInfo { public enum ScreenOrientation { BEHIND, FULL_SENSOR, LANDSCAPE, NOSENSOR, PORTRAIT, REVERSE_LANDSCAPE, REVERSE_PORTRAIT, SENSOR, SENSOR_LANDSCAPE, SENSOR_PORTRAIT, UNSPECIFIED, USER }; public static ScreenOrientation current = ScreenOrientation.UNSPECIFIED; }
The android.content.res.Configuration class could either re-use this enum, or define its own, if refactoring shows little overlap between the types. But you could not use a literal from one enum in a place where a value from another enum is expected. Under this approach, Activity would have a method with this signature:
void setRequestedOrientation(ScreenOrientation requestedOrientation)
This simple improvement would have no significant performance impact, and would make an entire class of errors reportable by the compiler. Why wasn't it done this way? I can't say for certain, but the way the code stands now, it bears the hallmarks of being written by someone whose first language is C or C++, not Java. The original author probably just missed the right way to do something in Java.
My thanks go to Noisygecko for sharing this case study with me. Do you have a favorite bug in your Android code that stands out in some way? Something that was hard to solve or led to a surprising resolution? Do you have a particularly good debugging approach?
Please follow up with a comment below, or a private message to me, and if I can blog about it, I will do, showcasing your work, and illuminating other Android developers! Cheers,
Peter van der Linden See all my MOTODEV blog posts at: http://bit.ly/n1G8RD
Android Technology Evangelist