Monday, September 13, 2010

Palm Shaking

This is a small point, but I could find no one else who has ever commented on this issue before: minor filtering with Palm shake events via the Accelerometer.

It's pretty easy to setup an event listener for the "shaking" event, just follow the instructions here. However there are two partially tricky points to getting it right.

First, you don't want to listen to every shake event. When listening to the "shaking" event, there's a parameter event.magnitude that you should use to filter. If you listen to all shaking events, then it's easy for your app to pick up shakes that are unintentional, such as someone putting their phone on a table or back in their pocket. I've found that a good number to pick is 2.08 - the reason for this being that the emulator, when you press "f5", simulates a shake event of magnitude ~2.09. Set your filter any higher and you won't be able to test with the emulator, but with a magnitude of at least 2, the user's shake has to be relatively intentional.

The second catch is that the shake event, unlike most events you might listen to in a scene, can happen in any scene on the stack. With buttons and the like, you can be lazy and not disable their listeners when you add another scene to the stack because those buttons are not accessible. You cannot be so lazy with the "shaking" event; even if you push another scene, your previous event listener will still fire off whenever the phone shakes. So make sure to disable any "shaking" handlers before you launch a new scene.

Thursday, August 19, 2010

Sending Emails on Android

UPDATE 2/25/2011: There is a much better solution to the problem I have now posted.

In this post I outline how to share information solely via email. There's some background information up front; if you're interested in just how to share data only with email apps, skip to the bottom.

One thing that users like to do a lot is share information. They want to be able to post things to Twitter, Facebook, or email their friends. Android makes this really simple via the ACTION_SEND intent:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, "A Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Here's my message.");
Intent mailer = Intent.createChooser(intent, null);
startActivity(mailer);


There's only one issue that arises from this method: any app that's registered to receive the ACTION_SEND intent will show up in the chooser. This means some oddball choices pop up ("Bluetooth" being the weirdest one for me), but that's kind of unavoidable. The bigger issue that I ran into was a matter of message length; Twitter messages are inherently shorter than what I might send via email. If I knew I only had 140 characters, the share message should be a lot shorter; but with the freedom of email, I can template out detailed information.

One solution that I've used is to pop up a dialog which asks the user whether they want a short template message or a long one, with examples of where to use each - short (Twitter, Facebook) vs. long (email).

A bigger problem arises when you want to only share information via emails. Suppose the information your app shares is just too complex for a short Twitter message - an unfortunate circumstance for the Twitter population but a fact of some apps. How do you force the chooser to select email? It turns out that there's a very roundabout way of handling this issue by taking advantage of the mailto protocol in Android:

Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data = Uri.parse("mailto:?subject=" + subject + "&body=" + body);
intent.setData(data);
startActivity(intent);


Using the data scheme that you do, only email apps pick up on this scheme. As a result, only email apps will open. You may not even force the user to go through a chooser if they have a default email app chosen already.

Wednesday, August 11, 2010

Palm and CRUD

I've been working with some Calendar integration on Palm and it's a mess because their documentation is out of line with reality. The basic CRUD API I have some issues with because of their ill-defined error codes - for example, if I want to find out if I've already created a calendar event or not I use service getEvent, but the error it returns if the event doesn't exist is the same error as if you input an invalid parameter. But beyond that there's simply mis-documented code or bad samples they give out that don't work.

Some things I've found:

createAccount - There's a lot of different hogwash in the documentation and samples, all of it wrong in one form or another. The icons parameter should actually be setup thus:

icons: {
"32x32": Mojo.appPath + "icon32.png",
"48x48": Mojo.appPath + "icon48.png"
}


Mojo.appPath is required, and then after that you link your icons from wherever they are in your project.

Accounts Linked to Email - There was some initial confusion because when I created an account with domain of "idunnolol" with username "dlew", Palm would give me the option to log into an email account "dlew@idunnolol.com". The fact of the matter is, Palm always thinks the account you create is linked to an email, even if you're just using for email. Sorry! You'll just have to get over this.

updateEvent - The documentation would have you believe that it takes two parameters - event and trackChanges. But when you do that, you get the error "ErrorGenericInvalidParameter: Missing Required Parameter: event.startTimestamp". What you really need to do is setup your event as the entire parameters. I have no idea where trackChanges could go (but I don't use it):

parameters: {
eventId: '1241251801',
startTimestamp: '92912911212',
endTimestamp: '1299592929291',
... etc
}


Besides those three things, I found that the code was much easier to work with if I setup a chain of onSuccess(), onFailure() calls that always guaranteed that I had a valid account and calendar. When I try to create a calendar event, first it checks that I have an account, then it checks if I have a calendar, and creates them if it doesn't exist. It's nice to have this built into your implementation because users can (at any time) delete your account if they feel like it.

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.