Friday, July 23, 2010

The Curious Case of the Missing HttpsURLConnection

I ran into an Android OS bug recently that is pretty harsh related to HTTPS connections. Basically, what happens is this:

1. You want to setup a connection between the phone and a server, and you need to control both the input and the output. As a result, you use URL.openConnection(), with setDoInput() and setDoOutput() set to true:

URL url = new URL("https://blahblahblah.com");
URLConnection conn = url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);


At some point you use both conn.getOutputStream() to write to the stream, then conn.getInputStream() to get the response.

2. You're doing an HTTPS connection. Some people report this happening on normal HTTP, but I've only seen it happen on HTTPS.

3. The first request goes through fine and dandy.

4. The second time you try to make the request, the connection doesn't send any data out and doesn't receive any data; it looks like it happens instantly. If you cast to an HttpURLConnection, conn.getResponseCode() returns -1 instead of anything meaningful.

In other words, every other request, the request fails outright. This is a noted bug in Android, but it isn't fixed yet in any released versions. Even when it's fixed, you'll still have to deal with this on older versions of Android.

There are a few workarounds. The first is to simply not use URLConnection; if you can find some way around it, avoid it. The second is to repeatedly make the same request until it works; it's a little too much of a hack for my tastes, but it'll work.

Then there's the third workaround, which I do not claim to understand why it fixes the issue but it does. Just set this setting when your application begins:

System.setProperty("http.keepAlive", "false");


Unfortunately this has some drawbacks (keep-alive is a good thing normally), but in comparison to mysteriously failed requests I'll ditch it.

Thursday, July 15, 2010

How to Change a ListView Row's Background but Keep Normal Android Selector

Here's a problem which I thought would be super complex to solve but is actually rather simple. The solution assumes a bit of knowledge of ListView row types, though; if you've never dealt with them, here's a primer.

Suppose I want to make a ListView composed of similar rows with different backgrounds. This is simple enough; create a ListView with multiple item view types and then as each one loads, set a different background resource based on their type. The catch is that you want the selected/pressed graphics to still look the same. An example of this would be an email app; you want read and unread emails to look different, but when the item is selected or pressed you still want that tacky orange (or whatever your system uses).

The naive solution of simply setting the background resource does not work, because it completely blocks the standard selector:

// Bad; don't use this!
convertView.setBackgroundResrouce(R.color.some_color);


I was able to glean the correct answer by examining the Email app's source code (thank goodness for open source). What you want to do is create a background which is transparent whenever the list's background selector kicks in, but uses your custom background color (or resource) when it's not. Here's what the drawable xml looks like:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:state_selected="true" android:drawable="@android:color/transparent" />
<item android:state_selected="true" android:drawable="@android:color/transparent" />
<item android:state_pressed="true" android:state_selected="false" android:drawable="@android:color/transparent" />
<item android:state_selected="false" android:drawable="@color/some_color" />
</selector>


With this in hand, you can set your view's background like so:

convertView.setBackgroundResrouce(R.drawable.some_row_background);


Just apply the above XML to multiple files, and you'll be able to set different types of backgrounds but still preserve the ListView's selector background.

Wednesday, June 9, 2010

app-assistant handleLaunch and large numbers

I've been working in the world of webOS for the last month, and while there's a lot of new APIs to learn I've not really run into an amazing "gotcha" moment until now.

In our app, we show some banner notifications. When you click on that banner notification, it takes you to a page with some information. On that page is some information about time, which means we're passing a timestamp in launchArguments (part of showBanner()).

The intriguing thing that happens is that, unlike the rest of Palm (nay, JS in general), integers passed using launchArguments are limited to 32 bits. Guess what's typically larger than 32 bits: timestamps measured in milliseconds from the epoch. It confounds me that such a limitation exists, but any number larger than 2^31 - 1 is converted to the max integer instead.

The solution? Wrap the number as a string. Bizarre that they'll let any length string through, but not a number larger than an integer. If anyone can explain, please do.

Thursday, June 3, 2010

Android 1.5 Widgets, Google Maps API and VerifyError

Here are two things that don't get along: Android 1.5 widgets and the Google Maps API. Trying to include both in your application results in a dreaded VerifyError when you try to create a widget on the home screen.

The basic cause, according to Dianne Hackborn:

Hi, it looks like there is a bug initializing a process when it is launched to handle a broadcast sent to an explicit component. In this case its shared libraries are not linked to it.


This manifests itself in two ways, basically whenever a class imports any com.google.android.maps classes. The first is if your widget needs to initialize any classes that import maps. The second is if any of your broadcast receivers initialize any classes that import maps. In both cases, the widget will attempt to load com.google.android.maps and fail, throwing a VerifyError.

The solution to the first is to not have any references to com.google.android.maps. This is sometimes easier said than done, but if you divide out your maps classes from your widget classes this is doable. Worst-case scenario, you can use reflection to call your maps classes; they will throw exceptions when the widget tries to load them, but hopefully you don't need them anyways. (This happened when I had my Application try to clear some maps cache data onLowMemory()).

The solution to the second is to put all of your receivers in a different process.

Wednesday, June 2, 2010

Headset Blocker

If you own a Nexus One, you may be familiar with this scenario: you load up your N1 with some rocking tunes. You plug in your headphones and go out exercising with it. Suddenly, the tunes start skipping. Why?

The problem is that the headset jack is interpreting what you are doing as a control signal. The exact cause hasn't been shown to me yet; there are a number of theories from a lack of software noise filtering to the difference between TRS and TRRS connectors. Regardless, it's a serious issue - some people can't even listen to music in their cars with the N1 because it skips so much.

Luckily, in the last few days I was shown an answer. The control commands from headsets are actually sent out as a chain broadcast, MEDIA_BUTTON. As with any chain broadcast, your receiver can abort the broadcast. So the simple solution is to make a BroadcastReceiver with an ultra-high priority that intercepts and aborts the MEDIA_BUTTON broadcast.

To that end, I've put a small app on the Market, Headset Blocker. It's a small, free widget that allows you to enable/disable blocking of the MEDIA_BUTTON broadcast. You can get it here:

QR code for Headset Blocker

The source code is here.

One interesting choice I had to make when writing this app was how to toggle the blocking. I had one of two options:

1. Use SharedPreferences to track when blocking is enabled. Capture all MEDIA_BUTTON broadcasts, but only abort when the preference is enabled.

2. Enable/disable my app's BroadcastReceiver. It will always abort when enabled, but is only enabled via the widget.

I went with the latter option, as I felt that would save just a little bit more battery when disabled (as the BroadcastReceiver itself would be disabled).

Sunday, May 23, 2010

Android: Serializable Considered Harmful

This is an old problem, but anyone I can convince not to use Serializable on Android will help the system as a whole.

There is some data saved between launches of my application - a list of complex objects. When I first learned Android I assumed that the fastest way to get the app up and running would be to slap Serializable on my data classes and call it a day. It certainly got things up and running quickly, but with some serious performance issues. I never noticed this in early testing because I only saved a few objects at a time. However, when I got up to 5+ objects the system started to slow down, and at 10+ it ran like molasses. There's not normally that many objects, but it's still something to be concerned about. It's not like I was doing anything complex, however - it was simply that Serializable is that slow.

Not only that, but I had to live in constant fear of changing the data structure. If enough things changed I would have to write my own serialization processes to work with backwards compatibility. Yuck.

I switched to using JSON to store all my data and this decision has made my life so much better. Android comes with a good JSON library and it's pretty simple to add JSON support to classes. JSON is much, much faster than serialization and comes out with much smaller results, too (you can even GZip the results to make them smaller still). To put it in perspective, the save time is now unnoticeable, whereas I could count the seconds using Serializable; and the file size was reduced by an order of magnitude.

Not only is it better for saving data, it's a very easy way to pass data between components. When I first started, I saw three ways to pass data between components: Serializing, parceling, or storing data in the Application context. Serializing was proving too slow, parceling is really a pain in the ass, and the last solution is harder to integrate with later on (no one else can call your component). JSON has proven a much better solution - it's fast enough to get by, but not nearly the pain in the ass that Parcels are.

Monday, May 3, 2010

ViewFlipper "Receiver not registered" Error

There is this error that plagued me for a few months. If you use a ViewFlipper, this error will pop up on Android 2.1 devices every once in a while:

java.lang.IllegalArgumentException: Receiver not registered: android.widget.ViewFlipper$1@44b6ab90


It does not affect any devices before 2.1, and so what was troubling at first was that the 2.1 source code wasn't yet released so I could't diagnose the issue. Everyone knew it had to do with orientation changes on an Activity with a ViewFlipper, but why exactly it happens isn't clear (I took my own stab at it, but was incorrect about the exact cause; it just seems to happen randomly).

Thankfully, the source has been available now for a while. Obviously, the problem is that onDetachedFromWindow() is somehow being called before onAttachedToWindow(); but how do we come up with a workaround until it is fixed at Google?

One simple solution is to override onDetachedFromWindow() and catch the error from super:

@Override
protected void onDetachedFromWindow() {
try {
super.onDetachedFromWindow();
}
catch (IllegalArgumentException e) {

}
}


The only problem with this is that the error is thrown before calling updateRunning(). A simple workaround for this is to call stopFlipping(), as that will kick off updateRunning() without any negative side effects:

@Override
protected void onDetachedFromWindow() {
try {
super.onDetachedFromWindow();
}
catch (IllegalArgumentException e) {
stopFlipping();
}
}


You should only use this version of the class on Android 2.1 phones, as they are the only ones requiring the fix.

EDIT May 25, 2010: ViewFlipper bug still occurs in Android 2.2. I sure hope you didn't filter based on apiLevel == 7; better to use apiLevel >= 7.