Cdacians

Cdacians
Cdacians

Tuesday 21 August 2012

Push: Client (Android based) and server


As part of the training and pushing the boundaries in my department, we recently experimented with Push technologies and their application to mobile development. Whereas iPhone seems to support natively push messaging, we soon realize that Android was not perfect in this direction. Surprisingly they haven't yet considered that a native push support is a technology worth to embedded within their official SDK, and this is a completely setback for developers aiming to develop and create their own ideas.

Let's explain this is in shortly: push is the name given to the fact that messages can be sent to the receiver, instead of being requested from the client. Although the concept might sounds easy to understand, the explanation on why the implementation is hard is a bit more complex: we can summarize it by saying that the protocol running under the modern Internet communications (TCP/IP) was not designed for Push technologies. For more information on this topic, we recommend to read the following article.

So there are three general solutions widely accepted to implement our own Push notifications:
  • Google C2DM: recently released by Google,
  • Poll: although it is not a real push request, the effect looks the same: we periodically poll the server looking for new data. The more often we poll the closer we get to the real-time push. But obviously, it will never be on real-time. Plus the battery will die very quickly
  • Persistent TCP/IP connection: the device initiates a long-lived mostly idle TCP/IP connection by playing with the feature of "keep-alive" (sending occasionally messages to the devices). Whenever something new is on the server, the phone initiates a fully TCP connection to acquire the new data. This is the underlying technology for Google C2DM, but you might want to have full control on the entire process until C2DM provides a reliable service.

The Google C2DM API is well described on the official service, but we might need to know a bit more about how to set up the server part. In short, we will need three parts: the C2DM server itself, a client implementation of Push (i.e., in your Android device) and a third party application service. This last component might be a bit tricky to understand, but a short summary of the functionalities performed are:

  • Is able to communicate with the client.
  • Will fire HTTP requests to the C2DM server.
  • Handle requests algorithm (for instance, we could design it for performing an exponential back off).
  • Storages the authentication tokens. They are need to handle applications with a little bit of complexity

As stated before, is easy to set up all the parts, but we might spend more time thinking about the third party server implementation. And that's the sugar of this article.

According to the source site of C2DM, the implementation of the Android is quite trivial. We first need to declare in our Manifest the required permissions for the application, which are com.google.android.c2dm.permission.RECEIVE, com.example.myapp.permission.C2D_MESSAGE and of course android.permission.INTERNET.  We also need to declare our receivers. For further information, check out the official link.
The next step is to register on our application, what can be done with the following code. Typically, we will add it into an onCreate method, or when the application needs to prepare itself for the push.

Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // boilerplate
registrationIntent.putExtra("sender", emailOfSender);
startService(registrationIntent);

Unregistering is also trivial (again, we might want to add this into the onDestroy event):
 
 
Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
unregIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0));
startService(unregIntent);

The basic part for handling the message needs a bit more of explanation. When the method onReceive of our BroadcastReceiver is triggered, we might need to check if we are dealing with the registration or if we are just receiving a push notification. We provide the code for the first case. For the second one, we might need to create our own method based on our design. We will be able to receive a message through the Intent. And that's the part we need to handle on the server side.
 
public void onReceive(Context context, Intent intent) {
   if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
      handleRegistration(context, intent);
   } else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
      handleMessage(context, intent);
   }
}
 
private void handleRegistration(Context context, Intent intent) {
   String registration = intent.getStringExtra("registration_id");
   if (intent.getStringExtra("error") != null) {
      // Registration failed, should try again later.
   } else if (intent.getStringExtra("unregistered") != null) {
      // unregistration done, new messages from the authorized sender will be rejected
   } else if (registration != null) {
      // Send the registration ID to the 3rd party site that is sending the messages.
      // This should be done in a separate thread.
      // When done, remember that all registration is done.
   }
}
 

So this is the core of the server application. This method needs to receive an auth code, and the device registration ID. The device registration ID will be provided when our phone is registered in the previous step. The authcode is the authentication through a Google account. For instance, we could create a dummy account, and send it along with the push notification:

googleAuthenticate("mydummyaccount@gmail.com","mydummypassword")

Afterwards, we can send a message type and the content itself. This allows us to have more flexibility in our notifications. For instance, we might provide a message type which corresponds with an error, and on the content we can send the complete error text.
 
function sendMessageToPhone($authCode, $deviceRegistrationId, $msgType, $messageText) {
    $headers = array('Authorization: GoogleLogin auth=' . $authCode);
    $data = array(
      'registration_id' => $deviceRegistrationId,
      'collapse_key' => $msgType,
      'data.message' => $messageText //TODO Add more params with just simple data instead
   );
 
   $ch = curl_init();
 
   curl_setopt($ch, CURLOPT_URL, "https://android.apis.google.com/c2dm/send");
   if ($headers)
      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
 
      $response = curl_exec($ch);
 
      curl_close($ch);
 
      return $response;
   }
 
function googleAuthenticate($username, $password, $source="Company-AppName-Version", $service="ac2dm") {
 
   if( isset($_SESSION['google_auth_id']) && $_SESSION['google_auth_id'] != null) {
   return $_SESSION['google_auth_id'];
}
 
// get an authorization token
$ch = curl_init();
if(!ch){
   return false;
}
 
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/accounts/ClientLogin");
$post_fields = "accountType=" . urlencode('HOSTED_OR_GOOGLE')
 
. "&Email=" . urlencode($username)
. "&Passwd=" . urlencode($password)
. "&source=" . urlencode($source)
. "&service=" . urlencode($service);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
 
// for debugging the request
//curl_setopt($ch, CURLINFO_HEADER_OUT, true); // for debugging the request
 
$response = curl_exec($ch);
 
//var_dump(curl_getinfo($ch)); //for debugging the request
//var_dump($response);
 
//var_dump(curl_getinfo($ch)); //for debugging the request
//var_dump($response);
 
curl_close($ch);
if (strpos($response, '200 OK') === false) {
   return false;
}
 
// find the auth code
preg_match("/(Auth=)([\w|-]+)/", $response, $matches);
 
if (!$matches[2]) {
   return false;
}
$_SESSION['google_auth_id'] = $matches[2];
 
return $matches[2];
}

I hope you enjoyed the tutorial. 

Thanks
akm
www.cdacians.com

No comments:

Post a Comment