Monday, October 11, 2010

The True Problem With Google's License Verification Library (LVL)

Google recently introduced a new method to fight piracy on the Android Market, the License Verification Library (LVL). It replaces the old system copy protection system, wherein your APKs would be put in a folder that you can't access. Unless you root. Oh, and anyone who can copy that APK off can then give it to someone else to put on their device, too. It was so weak, it was almost non-existant.

At first I took the news of LVL to be a great next step. In case you've not been paying attention, LVL puts the copy protection methods in the app itself - a form of DRM. Your app would now communicate with Google's servers to authorize use of the app. You'd no longer prevent a user from copying/transferring APKs, but doing so would be pointless because they wouldn't run on someone else's phone unless they were authorized.

However, any DRM measure can be cracked, it's just a matter effort. Only a few weeks after LVL was released it was shown how easy it was to modify bytecode to bypass the authorization altogether. Google responded with a series of posts (1, 2, 3) that explained how to circumvent this early crack in their armor. Essentially, it all boils down to different methods of obsfucating your code, which as we all know can still fail - and the steps Google proposes for protecting your APKs complicates your code and build process. As happens with most DRM, this has become a game of cat-and-mouse between Google and pirates, using ever-escalating tactics to one-up the other.

But there's a much bigger issue at the root of the entire system: The LVL library only works on apps sold through Google's Android Market.

The whole point of LVL is that the APK itself has some protection built in, so copying it around will not be an issue. But the LVL library only authenticates users who bought the app using the Android Market. If a user buys your app on a third-party store, then the LVL library will reject them. This means that in order to release on a third-party store, you'd have to ditch the LVL library for those builds - and thus defeat your own DRM.

There are a lot of Android Market alternatives now and more on the way. There are some publicly available are sites like SlideME and AppsLib (and possibly soon, Amazon). There are also some carrier-specific app stores - Verizon just launched V-Cast and in Europe, both Orange and Vodafone have their own offerings.

Android Market's recent expansion is, I believe, partially a response to this problem, but it comes too late. If the Market was more widespread and accessible early on then there would not have been an incentive to create and sell apps on alternative app stores in the first place; but now the momentum is there.

As much as it pains me, my conclusion is that it's better off to just go without any copy protection whatsoever. It does not makes sense to limit yourself to one store when potential customers could browsing another. Also, people who pirate will find a way to pirate, regardless your DRM; don't worry about them, and just worry about the people who would buy legit in the first place.

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.

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.

Why They Can't Find Your App

The Android Market is a mixed blessing; on the one hand, I much prefer the near-instantaneous publishing time for applications. It makes things a lot less stressful; if there's a terrible bug you can fix it quickly instead of having to go through Apple's approval process all over again. On the other hand, it has a number of bugs and limitations that are frustrating.

One of the most common support emails I get is "why can't I find your app on the Market?" This is a sad email, one of a customer lost; and over time (from contact with Google or perusing the net) I've come up with a good list of reasons why customers can't find my applications. Here it is, to help others diagnose the dreaded customer-who-would-be (in order of most likely to least likely):

1. The customer has an older version of Android that your app does not support; for example, if you support 2.x but the user is on a 1.5 phone.

2. Paid apps are only supported in some countries; if the customer is not in one of these countries, then you will not be able to find or purchase any paid applications. The list of supported countries can be found here.

Note that the customer can't simply move to a different country, either; this limitation is dependent on where your phone plan originally comes from. (Thanks to a European customer who confirmed this for me on a business trip!)

3. The customer is using a Google Apps account, rather than a gmail.com account. This can either cause the app not to appear at all, or in some cases make it unpurchaseable. More details can be found here.

4. The customer has a recently released phone and Google hasn't "fingerprinted" that device yet. This blocks all paid apps from that device. The only solution is to wait until Google fingerprints; it was kind of a pain with HTC Desire, at it took them a few weeks to get around to it.

5. The customer is using a non-standard build of Android (that is, rooted and installed a custom ROM). Some of these builds cause the Market to malfunction and fail to display paid apps.

6. The customer is using an Android Developer Phone and your application has copyright protection enabled. ADPs aren't allowed to view copyright protection applications. More info here.

7. This is by far the least likely situation, but you can't buy your own app under the same account as you published from!

Sunday, April 11, 2010

The Missing Manual - Android Drawables from XML

I have been fairly frustrated with a gap in the Android documentation recently - the file formats for creating Drawables from XML. Drawables XML has been pretty useful to me in the past in creating gradients; I'd rather use it than create 9-patches all the time. However, the Android documentation is strangely silent about the format; they provide a few samples, but as far as I know there's nothing beyond that.

I've taken it upon myself to study the source code and create a missing manual for all the possibilities for Drawable XMLs. The data was gathered from the Android source code, from the android.graphics.drawables package. I was surprised to find a lot of really useful code I wish I'd known about a long time ago; regardless, I was able to figure out all of how Drawables XML works.

Without further ado, here it is: the missing manual on Android Drawables via XML. I hope this lessens the pain someone else has slightly.

Wednesday, March 10, 2010

Uri and Android 1.5

I came across an interesting Android bug the other day that deserves some mention.

I was creating an Intent, setting some data for it, then calling startActivity(). The data Uri was created via Uri.Builder. Let's say, for example, the Uri I wanted was "http://google.com". The code would look like thus:

Intent intent = new Intent(context, TestActivity.class);
Uri.Builder builder = new Uri.Builder();
builder.scheme("http");
builder.authority("google.com");
intent.setData(builder.build());
startActivity(intent);

This works in 1.6+. But an interesting thing happens if you try this in 1.5... it blows up in your face with NullPointerExceptions! To answer why, I ended up digging around previous versions of the android.net.Uri class. It turns out that, previous to 1.6, Uri expects you to fill out every part of the Uri - including the query parameters (parts following ?) as well as the fragment (part following #).

The fix is to add dummy parameters for the sake of Android 1.5:

Intent intent = new Intent(context, TestActivity.class);
Uri.Builder builder = new Uri.Builder();
builder.scheme("http");
builder.authority("google.com");
builder.appendQueryParameter("ignore", "ignore");
builder.fragment("ignore");
intent.setData(builder.build());
startActivity(intent);

This isn't a problem if you already are using all parts of a Uri, or if you only support Android 1.6+.

Monday, January 18, 2010

ViewFlipper and Memory Leaks

Have you ever had a mysterious memory leak on Android when your code seemed clean as a whistle? I sure have, and I finally figured out why: ViewFlipper.

It turns out that when you call ViewFlipper.startFlipping(), it sets up a loop via messages which flips between its child Views. This is all well and good, but there's no code which detects that the ViewFlipper itself is no longer being displayed and should be canned. So the ViewFlipper keeps a hold on itself and its children, who inevitably keep a hold on their Context, and bam - gigantic memory leak. Depending on how much memory your Activity takes up, all you need to do is start changing the orientation of the ViewFlipper's containing Activity a few times and you could crash your application.

The solution to this is relatively simple: just call ViewFlipper.stopFlipping() in your Activity's onPause(), and then have it start flipping again in onResume().

There is, however, one small catch. Since ViewFlipper uses delayed messages, it's possible that a user can pause your activity then resume within the amount of time between flips (if they hit power then menu in quick succession, for example). The way ViewFlipper is setup, this means you can end up with multiple flip messages being passed around, resulting in faster flipping than intended. Unfortunately, the only solution I can think of to this is a real hassle (you would have to intentionally delay some time before resuming flipping in onResume()), so I'm just ignoring it for now.