Monday, November 29, 2010

ADT, Eclipse, and The Mystery of The Frozen Auto-Complete

I like Eclipse...as IDEs go, it's my IDE of choice.  For general day-to-day development, it covers so much straight out of the box, but then along come the wide variety of plugins that are available, and all of a sudden, Eclipse helps make life as a software developer just that little bit sweeter.

And when it comes to Android, the Android Development Toolkit (ADT) plugin is about as nice as they come, from quick and simple project setup, through to easy integration with the Android Debug Bridge, the ADT plugin really does deliver in simplifying Android development.  There has been, however, a slight problem that has left me downright frustrated many times, and that is the terrible slowdown in performance when it comes to auto-complete.

How terrible is terrible?  Well try anything from 5 to 20 seconds or so (on my machine) waiting for the auto-complete box to populate...it doesn't exactly sound like a lot, but it's more than enough to be extremely annoying, it breaks your flow, and it really makes you want to hurl your mouse at the screen as you realize that there's absolutely nothing that you can do about it but wait and seethe.

From what I've read, this is an issue that only affects Eclipse 3.6, and so the interim solution offered was simply to revert back to 3.5.  The only problem with that solution is that I rather like Helios and the increased stability that comes along with it; I really don't want to have to go back to Galileo if I can avoid it.  And I guess others in the same situation agree, because lo-and-behold...a workaround has been found.

How to Have Your Cake And Eat It
As it turns out, the ADT classpath container is looking for source in the standard Android SDK installation directory...except the standard Android SDK doesn't come with the source.  This means that Eclipse attempts to cache entries that are in a directory that doesn't exist.  Everytime.

And so the obvious workaround is to simply download the source, create the directory, and let Eclipse be happy.  To do this, you simply need to do the following:

And that's it...problem solved!  For the guys on this thread where they provided this solution, I offer my utmost thanks.  As do my mouse and monitor. :)

Saturday, November 6, 2010

C2DM: On The Receiving End

My last couple of posts regarding Android's Cloud to Device Messaging framework were purely from the perspective of the server, in that we first had to obtain an authentication token using ClientLogin, and then we actually used the C2DM service to send a message.  But what about the client side?  What's needed from our Android app in order to receive these near-realtime notifications?  Well as it turns out, we simply need to do a couple of things:
  • Register with the C2DM framework to receive messages
  • Pass the registration ID that we get to the server
All in all, that's a pretty small to-do list, don't ya think? :)

Warming Up
Of course, before we can even start to think about using the C2DM framework, we have to do a few things first to make sure our app is fully prepared.  As the ability to use the C2DM framework arrived in the Froyo release, we have to make sure that our application requires version 2.2 or later by specifying it in the manifest:


    
    

    ...

Next, we need to make sure that we have our permissions set up properly. In order to receive notifications, the app must have the following permissions:
  • com.google.android.c2dm.permission.RECEIVE - allows us to register and receive messages.
  • android.permission.INTERNET - we need this so that we can pass the registration key to the third-party server that will be sending us notifications (it needs to know who to send the messages to, right?).
  • <applicationPackage>.permission.C2D_MESSAGE
  • - stops other applications from trying to register as our app in order to steal our messages.
This should be straightforward enough, so let's have a go:


    
    
    

    
    

    
    

    ...

So far, so simple, right?  Good. :)  But, we're not quite out of the woods as far as the permissions go just yet; we still need to make sure that we only receive messages from the C2DM framework, otherwise any other app could send us notifications (which in all likelihood we probably don't want to have happen).  To achieve this, we'll specify a receiver that has the following permissions (each with a category that matches our applicationPackage):
  • com.google.android.c2dm.intent.REGISTRATION
  • com.google.android.c2dm.intent.RECEIVE
So what does this look like?  Well, something like this:


    
    
        
        
            
            
        
        
        
            
            
        
    

    ...

Here, we're specifying a receiver (my.example.app.C2DMReceiver) that will perform the handling of the registration ID and any messages that are sent our way.  Again, all in all relatively straightforward...don't ya just love Android? :)

Registration
Now that we have the permissions sorted, it's time to register our app so that it can receive messages...and here's where it gets super-silly-easy.  All we need to do is create an Intent (com.google.android.c2dm.intent.REGISTER) and pass the email address of the sender (which will be the email address that we set up for the server to use) and our application ID:
public static void register(Context context, String senderEmail) {
    Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
    registrationIntent.putExtra("app",
            PendingIntent.getBroadcast(context, 0, new Intent(), 0));
    registrationIntent.putExtra("sender", senderEmail);
    context.startService(registrationIntent);
}
Wow, pretty easy huh?  (Don't worry, I can sense the nodding) :)  So what happens after we've fired off that Intent?  Well this is where the receiver that we declared in the manifest comes in to play:
public class C2DMReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
            String registrationId = intent.getStringExtra("registration_id");
            String error = intent.getStringExtra("error");
            String removed = intent.getStringExtra("unregistered");
            
            if (error != null) {
                // Perform error processing
                ...
            } else if (removed != null) {
                // Perform any post-unregistration cleanup
                ...
            } else if (registrationId != null) {
                // Send registrationId to third-party server
                ...
            }
        }
    }

}
Our receiver simply checks the Intent to see if it's a com.google.android.c2dm.intent.REGISTRATION Intent, and if it is then it grabs the registration_id and, barring any errors (or if this is a response to our application unregistering itself), it goes ahead and passes the received registration ID to our third-party server.  This now ensures that our app is primed and ready to start receiving messages.

Receiving Messages
Handling any messages that come in is done using the same means that we used for receiving the registration ID.  This means we're going to use C2DMReceiver again...specifically, we're going to be using exactly the same method:
public class C2DMReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        ...
        if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
            // Yay, we've received a message...grab the
            // parameters and do awesome stuff!
            String myParam1 = intent.getStringExtra(SOME_PARAM_1);
            String myParam2 = intent.getStringExtra(SOME_OTHER_PARAM);
            // Perform any processing
            if ("some_condition".equalsIgnoreCase(myParam1)) {"
                doSomethingAwesome(myParam2);
            }
            ...
        }
    }

}
And in a nutshell, that's it...it just about couldn't get any simpler.  Really.  But I'm not quite done with C2DM yet; there are some things that are handy to know when it comes to integration testing...

Testing, Testing, 1-2-3
It's relatively simple to test things out just by using the emulator alone.  As always though, there are some things that we need to make sure of first, and these are:
  • The AVD (Android Virtual Device) must use the package "Google APIs by Google Inc., Android API 8", so make sure you have it installed.  It has to be "Google APIs" and not simply "Android API 8" or it won't work.
  • C2DM requires a valid signed-in Google account in order to establish a connection with the device, which means that the emulator also needs to have a valid Google account that is logged in.  Simply go to Settings > Accounts & Sync > Add Account and provide the account details.
  • If the application server that you will be sending the messages from is running locally, make sure you're not trying to use localhost when sending the registration ID in your app.  Instead, use address 10.0.2.2 to hit the local server instance and save yourself some noodle-scratching. :)
Happy messaging!

Thursday, November 4, 2010

C2DM: Sending Messages

This post follows on from my previous post where we called ClientLogin to obtain an authentication key.  The reason why we needed this key is so that we can do something really cool...send out push notifications to our Android applications.  And similar to that post, the code that is provided here should be just enough to produce a result; a real application will need to implement functionality to handle the different responses that the C2DM service can return, as well as handle things like exponential backoff for when the service is unavailable.

The basics of sending a message are really quite simple; all we need to do is send a well formed HTTPS request to https://android.apis.google.com/c2dm/send and pass along the registration ID of the device that we wish to send a message to, our authentication token, and the payload of the message itself.  So just like we did before when obtaining the authentication token, we start off with the HttpURLConnection object:
HttpURLConnection connection = null;
try {
    URL url = null;
    try {
        url = new URL("https://android.apis.google.com/c2dm/send");
    } catch (MalformedURLException e) {
        // Exception handling
    }
    connection = (HttpURLConnection) url.openConnection();
    connection.setDoOutput(true);
    connection.setUseCaches(false);
    connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    connection.setRequestProperty("Authorization", "GoogleLogin auth=" + token);
} catch (Exception e) { 
    // Exception handling
}
Once again, I'm using HttpURLConnection as my application is going to be hosted using Google App Engine, and HttpsURLConnection is simply not allowed.  Also, notice how we need to send our authentication token; it needs to be prefixed with the String "GoogleLogin auth=".

Next up, we need to set the parameters that we're going to pass to the C2DM service.  For this, we need to include the registration id that the device received from the C2DM service when the device first registered itself (yes, that means the device will also need to pass that id to us as soon as it gets it).  We also need to specify our message payload in the form of key-value pairs, with the key prefixed with "data.":
StringBuilder sb = new StringBuilder();
addEncodedParameter(sb, "registration_id", deviceRegistrationId);
addEncodedParameter(sb, "collapse_key", "goobr.blogspot.com.someKey");
addEncodedParameter(sb, "data.payload1", "payload1 message data");
addEncodedParameter(sb, "data.payload2", "payload2 message data");
addEncodedParameter(sb, "data.anotherPayload", "even more message data");
String data = sb.toString();
One thing to note is the collapse_key parameter.  This is simply a required key that will be used to collapse our messages so that if the device is switched off, it won't get a ton of messages when it comes online again...in this situation, only the last message will actually be received by the device (although C2DM makes no guarantee on message order, so it might not actually be the last message that was sent).  And just like before, we're using the same addEncodedParameter() method to format our parameters into a String:
public static void addEncodedParameter(StringBuilder sb, String name, String value) {
    if (sb.length() > 0) {
        sb.append("&");
    }
    try {
        sb.append(URLEncoder.encode(name, "UTF-8"));
        sb.append("=");
        sb.append(URLEncoder.encode(value, "UTF-8"));
    } catch (UnsupportedEncodingException e) {
        // Exception handling
    }
}
Finally, we come to the actual sending of the message itself, and processing the response:
try {
    DataOutputStream stream = new DataOutputStream(connection.getOutputStream());
    stream.writeBytes(data);
    stream.flush();
    stream.close();

    switch (connection.getResponseCode()) {
    case 200:
        // Success, but check for errors in the body
        break;
    case 503:
        // Service unavailable
        break;
    case 401:
        // Invalid authentication token
        break;
    }
} catch (IOException e) {
    // Exception handling
}
If the service is unavailable, then we need to check for (and honor) any Retry-After header in the response, and failing that, we need to implement exponential backoff (that is, keep doubling our wait time before trying the request again).  Also, even if we get a 200 response code, that doesn't mean that the message went through.  We still need to check the response body for any of the following:
  • Error=QuotaExceeded
  • Error=DeviceQuotaExceeded
  • Error=InvalidRegistration
  • Error=NotRegistered
  • Error=MessageTooBig
  • Error=MissingCollapseKey
On the first two errors, there's not a lot we can do but wait for a while and try again...it just means that too many messages have already been sent.  For the InvalidRegistration and NotRegistered errors, we need to make sure that we stop sending messages to this registration id as the device has either unregistered itself, uninstalled our Android app (uh-oh!), or simply switched off notifications.  And for the final two errors, well we simply need to look at our code and make sure that:
  • the message is 1024 bytes in size or less and
  • we include a collapse_key in the request
One final thing to note is that the response may include an Update-Client-Auth header from time to time, and all this means is that the authentication token that was used to send the message is about to expire, and so the C2DM framework has generated a new one that we should start using instead.

And that's all there is to it really.  See...told you it was really quite simple! Have fun sending push notifications to your Android apps. :)