Android C2DM with PHP and Zend Framework

by Mike Willbanks on November 21st, 2010

Update: For the latest please see my github implementation, the code for the underlying Zend Framework implementation has been updated and is maintained there.

So you’ve got a new fancy Android application and you want to be able to send push notifications to the phone. Either for synchronization purposes or for notifications. Since C2DM is fairly new and is currently in the labs it is rather difficult to find code that already handles sending out the notifications correctly.

This article will go through sending a push notification (or message) through the Android C2DM server utilizing PHP in the fashion of a Zend Framework component.

Overview

Sending a message through C2DM can be rather complex as there are fairly strict requirements that Google puts into place. There are several areas that you need to be aware of as well as changes that may happen during the processing of a response. I attempt to encapsulate some of this work in the class handling the sending, however, it is up to the code that sends the notification to properly handle the exceptions thrown. This allows for extremely high flexibility and the ability to hook your own code into this feature.

Requirements

Before we begin there are a few requirements that you will need to have in order to utilize this code to be able to send information to the Android device and create your notification server.

Implementing the C2DM Third-Party Application Server

In order to implement the C2DM Third-Party Application Server we will be utilizing PHP and the Zend Framework. Since Zend Framework already has built-in support for getting the authentication token we will make re-use of that component and simply provide a method for setting the token.

Next we need to ensure that we are properly handling the variances that are coming back from the C2DM server. This can be login based, message based or based on what our user has done on their device. Lets get to the code so we can dissect what needs to happen.

The Library

This library does not have includes in it as we utilize autoloading, if you need includes it is fairly easy to add them all in. This is going to be a lot of code – beware.

library/Zend/Service/Google/C2dm.php

/**
 * Service Google C2dm
 * Implementation for Android Cloud to Device Messaging
 *
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm
{
    /** 
     * @var string
     */
    const C2DM_SEND_URI = 'https://android.apis.google.com/c2dm/send';
 
    /** 
     * @var string
     */
    const AUTH_SERVICE_NAME = 'ac2dm';
 
    /** 
     * @var string
     */
    protected $_defaultPostUri = self::C2DM_SEND_URI;
 
    /** 
     * @var Zend_Http_Client
     */
    protected $_client;
 
    /** 
     * @var string
     */
    protected $_loginToken;
 
    /** 
     * @var array
     */
    protected $_options = array();
 
    /**
     * @var Zend_Service_Google_C2dm_Message
     */
    protected $_lastMessage;
 
    /**
     * @var Zend_Http_Response
     */
    protected $_lastResponse;
 
    /**
     * Constructor
     *
     * @param array|Zend_Config $options
     * @return Zend_Service_Google_C2dm
     */
    public function __construct($options=array())
    {
        $this->setOptions($options);
    }
 
    /**
     * Get Options for C2DM
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->_options;
    }
 
    /**
     * Set Options for C2DM
     *
     * @param array|Zend_Config $options
     * @return Zend_Service_Google_C2dm
     * @throws Zend_Service_Google_C2dm_Exception
     */
    public function setOptions($options)
    {
        if ($options instanceof Zend_Config) {
            $options = $options->toArray();
        } elseif (!is_array($options)) {
            throw new Zend_Service_Google_C2dm_Exception('setOptions() expects either an array or a Zend_Config object');
        }
 
        foreach ($options as $key => $value) {
            $method = 'set' . ucfirst($key);
            if (method_exists($this, $method)) {
                $this->$method($value);
            } else {
                $this->_options[$k] = $v;
            }
        }
 
        return $this;
    }
 
    /**
     * Get Login Token
     *
     * @return string
     */
    public function getLoginToken()
    {
        return $this->_loginToken;
    }
    /**
     * Set Login Token
     *
     * @param string $token
     * @return Zend_Service_Google_C2dm
     * @throws Zend_Service_Google_C2dm_Exception
     */
    public function setLoginToken($token)
    {
        if (!is_string($token)) {
            throw new Zend_Service_Google_C2dm_Exception('setLoginToken() expects a string');
        }
        $this->_loginToken = $token;
        return $this;
    }
 
    /**
     * Get Http Client
     *
     * @return Zend_Http_Client
     */
    public function getHttpClient()
    {
        if (!$this->_client instanceof Zend_Http_Client) {
            $this->_client = new Zend_Http_Client();
            $this->_client->setConfig(array(
                'strictredirects' => true,
            ));
        }
        return $this->_client;
    }
 
    /**
     * Set Http Client
     *
     * @return Zend_Service_Google_C2dm
     */
    public function setHttpClient(Zend_Http_Client $client)
    {
        $this->_client = $client;
        return $this;
    }
 
    /**
     * Prepare an HTTP Request for C2DM
     *
     * @return void
     * @throws Zend_Service_Google_C2dm_Exception
     */
    protected function _prepareHttpRequest()
    {
        $client = $this->getHttpClient();
        $token = $this->getLoginToken();
        if (empty($token)) {
            throw new Zend_Service_Google_C2dm_Exception('Sending a message requires a Google Authorization Token');
        }
        $client->setUri($this->_defaultPostUri);
        $client->setHeaders('Authorization', 'GoogleLogin auth=' . $token);
        if (array_key_exists('delay_while_idle', $this->_options) && $this->_options['delay_while_idle']) {
            $client->setParameterPost('delay_while_idle', 1);
        }
        $this->_client = $client;
    }
 
    /**
     * Send a Message
     *
     * @param Zend_Service_Google_C2dm_Message $message
     * @return boolean
     * @throws Zend_Service_Google_C2dm_Exception
     */
    public function sendMessage(Zend_Service_Google_C2dm_Message $message)
    {
        $this->_lastMessage = $message;
        if (!$message->validate()) {
            throw new Zend_Service_Google_C2dm_Exception('sendMessage was unable to validate the message');
        }
        $this->_prepareHttpRequest();
        $client = $this->getHttpClient();
        $client->setParameterPost('registration_id', $message->getRegistrationId());
        $client->setParameterPost('collapse_key', $message->getCollapseKey());
        foreach ($message->getData() as $k => $v) {
            $client->setParameterPost('data.' . $k, $v);
        }
 
        $this->_lastResponse = $response= $client->request('POST');
 
        // check the response for errors:
        switch ($response->getStatus()) {
            case 503:
                throw new Zend_Service_Google_C2dm_Exception_ServerUnavailable('The server was unavailable, check Retry-After and try again');
                break;
            case 401:
                throw new Zend_Service_Google_C2dm_Exception_InvalidAuthToken('The Auth token: ' . $this->getLoginToken() . ' was invalid');
                break;
            default:
                // check response body for any errors.
                $body = $response->getBody();
                $body = preg_split('/=/', $body);
                if (!isset($body[0]) || !isset($body[1])) {
                    // bad response from google
                    throw new Zend_Service_Google_C2dm_Exception_ServerUnavailable('The server gave us an invalid response, we need to try again.');
                }
                if (strtolower($body[0]) == 'error') {
                    $exception = "Zend_Service_Google_C2dm_Exception_{$body[1]}";
                    throw new $exception();
                }
        }
        return true;
    }
 
    /**
     * Gets the Last Sent Message
     *
     * @return Zend_Service_Google_C2dm_Message
     */
    public function getLastMessage()
    {
        return $this->_lastMessage;
    }
 
    /**
     * Gets the Last Response
     *
     * @return Zend_Http_Response
     */
    public function getLastResponse()
    {
        return $this->_lastResponse;
    }
}

library/Zend/Service/Google/C2dm/Message.php

/**
 * C2dm Message
 * Implements an individual message to a client
 *
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Message
{
 
    protected $_registrationId;
    protected $_collapseKey;
    protected $_data = array();
 
    /**
     * Constructor
     *
     * @param string $registrationId
     * @param string $collapseKey
     * @param array  $data
     * @return Zend_Service_Google_C2dm_Message
     */
    public function __construct($registrationId=null, $collapseKey=null, array $data=array())
    {
        $this->setRegistrationId($registrationId);
        $this->setCollapseKey($collapseKey);
        $this->setData($data);
    }
 
 
    /**
     * Get Registration ID
     *
     * @return string
     */
    public function getRegistrationId()
    {
        return $this->_registrationId;
    }
 
    /**
     * Set Registration ID
     *
     * @param string $registrationId
     * @return Zend_Service_Google_C2dm_Message
     * @throws Zend_Service_Google_C2dm_Exception
     */
    public function setRegistrationId($registrationId)
    {
        if (!is_string($registrationId)) {
            throw new Zend_Service_Google_C2dm_Exception('setRegistrationId() requires a string for registrationId');
        }
        $this->_registrationId = $registrationId;
        return $this;
    }
 
    /**
     * Get Collapse Key
     *
     * @return string
     */
    public function getCollapseKey()
    {
        return $this->_collapseKey;
    }
 
    /**
     * Get Collapse Key
     *
     * @return string
     */
    public function getCollapseKey()
    {
        return $this->_collapseKey;
    }
 
    /**
     * Set Collapse Key
     *
     * @param string $collapseKey
     * @return Zend_Service_Google_C2dm_Message
     * @throws Zend_Service_Google_C2dm_Exception
     */
    public function setCollapseKey($collapseKey)
    {
        if (!is_string($collapseKey)) {
            throw new Zend_Service_Google_C2dm_Exception('setCollapseKey() requires a string for collapseKey');
        }
        $this->_collapseKey = $collapseKey;
        return $this;
    }
 
    /**
     * Get Key Value Data Pairs
     *
     * @return array
     */
    public function getData()
    {
        return $this->_data;
    }
 
    /**
     * Set a Data Array
     *
     * @param array $data
     * @return Zend_Service_Google_C2dm_Message
     */
    public function setData(array $data)
    {
        $this->_data = $data;
    }
 
    /**
     * Validate the Message
     *
     * @return bool
     */
    public function validate()
    {
        if (empty($this->_registrationId)) {
            return false;
        } elseif (empty($this->_collapseKey)) {
            return false;
        } elseif (empty($this->_data)) {
            return false;
        }
        return true;
    }
}

library/Zend/Service/Google/C2dm/Exception.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception extends Zend_Service_Exception
{}

library/Zend/Service/Google/C2dm/Exception/DeviceQuotaExceeded.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception_DeviceQuotaExceeded extends Zend_Service_Exception
{}

library/Zend/Service/Google/C2dm/Exception/InvalidRegistration.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception_InvalidRegistration extends Zend_Service_Exception
{}

library/Zend/Service/Google/C2dm/Exception/QuotaExceeded.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception_QuotaExceeded extends Zend_Service_Exception
{}

library/Zend/Service/Google/C2dm/Exception/InvalidAuthToken.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception_InvalidAuthToken extends Zend_Service_Exception
{}

library/Zend/Service/Google/C2dm/Exception/MessageTooBig.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception_MessageTooBig extends Zend_Service_Exception
{}

library/Zend/Service/Google/C2dm/Exception/NotRegistered.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception_NotRegistered extends Zend_Service_Exception
{}

library/Zend/Service/Google/C2dm/Exception/ServerUnavailable.php

/**
 * @category   Zend
 * @package    Zend_Service
 */
class Zend_Service_Google_C2dm_Exception_ServerUnavailable extends Zend_Service_Exception
{}

The Worker

Since I am a bit proponent of Gearman – the worker is written in my custom Gearman workers for Zend Framework. You will be able to see how all of these classes are utilized within this worker.

application/workers/C2dmWorker.php

/**
 * C2DM Worker
 * Sends messages to Android devices through Cloud To Device Messaging
 */
class C2dmWorker extends Gearman_Worker
{
    /**
     * @var string
     */
    protected $_registerFunction = 'c2dm';
 
    /**
     * @var Zend_Service_Google_C2dm
     */
    protected $_c2dm;
 
    /**
     * Exponential Backoff / Retry-After
     *
     * @param int $fails
     * @param Zend_Http_Response
     * @return int
     */
    protected function getBackOffTime($fails, Zend_Http_Response $response)
    {
        if ($retry = $response->getHeader('Retry-After')) {
            if (is_string($retry)) {
                $retry = strtotime($retry) - time();
            }
            return (int) $retry;
        }
        return intval(pow(2, $fails) - 1);
    }
 
    /**
      * Initialize the Worker
      *
      * @return void
      */
    protected function init()
    {
        // attempt to login to google (should store this somewhere like sqlite for multiple workers
        try {
            $client = Zend_GData_ClientLogin::getHttpClient('YOUR_GOOGLE_ACCT_EMAIL', 'YOUR_GOOGLE_ACCT_PSWD', Zend_Service_Google_C2dm::AUTH_SERVICE_NAME, null, 'YOUR_GOOGLE_APP');
        } catch (Zend_Gdata_App_CaptchaRequiredException $cre) {
            // manual login is required
            echo 'URL of CAPTCHA image: ' . $cre->getCaptchaUrl() . PHP_EOL;
            echo 'Token ID: ' . $cre->getCaptchaToken() . PHP_EOL;
            exit(1);
        } catch (Zend_Gdata_App_AuthException $ae) {
            echo 'Problem authenticating: ' . $ae->exception() . PHP_EOL;
            exit(1);
        }
        $this->_c2dm = new Zend_Service_Google_C2dm();
        $this->_c2dm->setLoginToken($client->getClientLoginToken());
    }
 
    /**
     * Work
     *
     * @return void
     */
    protected function _work()
    {
        $workload = $this->getWorkload();
        // assume workload is json with registrationId, collapseKey and a data array
        $data = json_decode($workload);
        $message = new Zend_Service_Google_C2dm_Message(
            $data->registrationId,
            $data->collapseKey,
            $data->data
        );
        $failCount = 0;
        do {
            $tryAgain = false;  
            try {
                $this->_c2dm->sendMessage($message);
            } catch (Zend_Service_Google_C2dm_Exception_QuotaExceeded $e) {
                $failCount++;
                $tryAgain = true;
                usleep($this->_getBackOffTime($failCount, $c2dm->getLastResponse()) * 1000);
            } catch (Zend_Service_Google_C2dm_Exception_ServerUnavailable $e) {
                $failCount++;
                $tryAgain = true;
                usleep($this->_getBackOffTime($failCount, $c2dm->getLastResponse()) * 1000);
            } catch (Zend_Service_Google_C2dm_Exception_InvalidRegistration $e) {
                // do not attempt to send a message to this id again
            } catch (Zend_Service_Google_C2dm_Exception_NotRegistered $e) {
                // do not attempt to send a message to this id again
            } catch (Zend_Service_Google_C2dm_Exception_DeviceQuotaExceeded $e) {
                // you may attempt to retry, however, it may be best to let it go away
            } catch (Zend_Service_Google_C2dm_Exception_MessageTooBig $e) {
               // you may want to log this one and find the offending code and reduce the message size
            } catch (Zend_Service_Google_C2dm_Exception_MissingCollapseKey $e) {
               // you may want to log this one and find the offending code to ensure that this is pushed in
            } catch (Zend_Service_Google_C2dm_Exception $e) {
              // all of the rest of the exceptions are fatal in nature, log the exception and kill the client
              // sometimes we simply just need to generate a new id.
              exit(1);
            }
        } while ($tryAgain);
    }
}

Receiving C2DM Messages on Android and Displaying a Notification

The following is going to be the code that we need to implement in order for Receiving C2DM messages as well as displaying them as a notification in the notification manager.

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.dummy.app" android:versionCode="1"
    android:versionName="1">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <service android:name=".C2DMReceiver" />
        <receiver android:name="com.dummy.app.C2DMReceiver"
            android:permission="com.google.android.c2dm.permission.SEND">
            <intent -filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="com.dummy.app" />
            </intent>
            <intent -filter>
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="com.dummy.app" />
            </intent>
        </receiver>
    </application>
    <permission android:name="com.dummy.app.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses -permission android:name="com.dummy.app.permission.C2D_MESSAGE" />
    <uses -permission android:name="com.google.android.c2dm.permission.RECEIVE" />
</manifest>

src/com/dummy/app/C2DMReceiver.java

 
package com.dummy.app;
 
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
 
import com.dummy.app.myIntent;
import com.dummy.app.R;
 
public class C2DMReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
            String registration = intent.getStringExtra("registration_id");
            if (intent.getStringExtra("error") != null) {
                // handle an error for registration
            } else if (intent.getStringExtra("unregistered") != null) {
                // call service to handle unregistration
            } else if (registration != null) {
                // call service to handle registration
            }
        } else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
            // we are just going to say a few fields already exist here, but this is based
            // on what you may be sending from above in your data fields
            String title = intent.getExtras().getString("title");
            String message = intent.getExtras().getString("msg");
 
            Intent myIntent = new Intent(context, myIntent.class); // replace myIntent.class with your intent to launch
            PrendingIntent contentIntent = PendingIntent.getActivity(context, 0, myIntent, 0);
 
            // create the notification!
            int icon = R.drawable.icon;
            long when = System.currentTimeMillis();
            CharSequence contentTitle, contentMessage = null;
            contentTitle = (CharSequence) title;
 
            contentMessage = (CharSequence) message;
 
            NotificationManager nm = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
            Notification n = new Notification(icon, contentTitle, when);
            n.setLatestEventInfo(context, contentTitle, contentMessage, contentIntent);
            nm.notify(0, n); // 0 can be replaced with a number for separate style of messages
        }
    }
}

Registration with C2DM

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);

Conclusion

This is an easy way to send notifications to an android device. I realize that this is a lot of code and not a lot of detail in text, please ask any questions in the comments and I will get back to them. There is also a wide range of items that you can do on the device through c2dm, this is not just limited to showing a notification or synchronizing data.

From PHP

29 Comments
  1. Ren permalink

    Bug.

    $message = Zend_Service_Google_C2dm_Message(

    Should be

    $message = new Zend_Service_Google_C2dm_Message(

    Surely

  2. @Ren,

    Thanks, that has been updated. I guess that is what I get for typing this into the textarea.

    Regards,

    Mike

  3. When i use this code then 1 problem comes in the manifest that C2DMReceiver doesnot extends service then how can i resolve this problem.

  4. Vikram permalink

    Where is the HttpClient set in the Zend_Service_Google_C2dm? The client is instantiated in the init function of the worker thread and is not stored anywhere, correct? In the init function, we have the following 2 statements…

    $this->_c2dm = new Zend_Service_Google_C2dm();
    $this->_c2dm->setLoginToken($client->getClientLoginToken());

    Shouldn’t we have an additional statement like the following?

    $this->_c2dm->setHttpClient($client);

  5. @Vikram -
    You do not actually need to set the Http Client. By using the client from Google GData it does modify some areas, the setHttpClient is provided to override the default behavior. Inside the implementation it creates a Zend_Http_Client inside of the getHttpClient if one was not set in.

  6. @Amit -
    Could you explain a little more about the issue you are running into? It does not need to “extend” a Service in terms of the class since it is a BroadcastReceiver

  7. deeker permalink

    Thanks for this tutorial Mike, it’s much appreciated. I am a bit lost though…

    So far I’ve managed to set up my server’s directory structure how you show and Zend is working correctly. And my Android project is set up with a C2DMReceiver and is running smoothly.

    I’m getting lost when you create the C2dmWorker.php file that extends Gearman_Worker. Could you please elaborate on installing and configuring Gearman, and on the function of the C2dmWorker.php file? Also, could you discuss how to set up the client in the Android project to communicate with the 3rd Party Application Server. And if you’re feeling generous, could you show how to create a message to be sent over the cloud? I’d also be interested in seeing how to set up the Application Server to store the registration-id’s and other device info in an SQL database.

    Hope I’m not asking for too much, any information you can give is much appreciated. Thanks.

  8. Thanks for sharing this code. One issue I noticed – you’re multiplying the number of seconds from getBackOffTime by 1000, but usleep takes microseconds – you should be multiplying by 1000000. Also, Google says to add in some fuzzing – their chrometophone server code adds a random amount of time from 0 to 3000 milliseconds.

  9. David permalink

    Hi Mike,

    Thank you for sharing your article. Will the method described in the link below to install Gearman on Window XP where Zend Framework is installed work for C2dmWorker?

    Installing Gearman and gearmand on Windows with Cygwin
    http://www.phpvs.net/2010/11/30/installing-gearman-and-gearmand-on-windows-with-cygwin/

    Thanks,
    David

  10. David permalink

    Is there anyone who implemented the Worker.php using Zend Framework instead of using Gearman?

  11. Jarred Suisman permalink

    Great post Mike, thanks for sharing. I know this blog entry is a few months old but in the last section “Registration with C2DM” I think you need to change:
    com.google.android.c2dm.intent.REGISTER
    to
    com.google.android.c2dm.intent.REGISTRATION

    Thanks again,

    Jarred

  12. Francesco permalink

    Hi Mike! Thanks for the help here!
    I was wondering if you used Zend Studio, because I’ve desperately tried
    to make this work with Eclipse and I didn’t succeed. Probably due to
    problems with the include of the zend framework.

  13. @Francesco – I will look at Zend Studio tomorrow to see if I might be able to see anything. You may need to right click and go to the include paths and setup the items there manually. I am not very familiar with Zend Studio as I’m a crazy vim user. :)

  14. Mark Peters permalink

    Hi Mike,

    Can you recommend a good resource for learning about the server side programming required to setup a real 3rd-party C2DM application? I’m a client side developer who won’t have any trouble with the Android end of C2DM, but I’m pretty much a novice for server side development.

    I have my own domain for hosting the app I have in mind, plus basic knowledge of SQLite (from my Android experience). The domain server has PHP, SQLite and various other server side technologies available.

    What is understandably missing from all the C2DM tutorials is the implementation of the user account creation/maintenance component required for a real application. That’s what I want to learn about.

    Thanks,

    Mark Peters

  15. @Mark
    In this case it is not really missing, if you look at the exceptions that I am throwing it becomes very clear in what cases you need to remove the user in terms of sending.

    Generally speaking, the code that was provided talks about when the registration happens, you will need to fire off the registration id to the server side to let it know that an account was created. Also this may update as well so whenever you get one, you should send it and replace the old one as well. This will all need to happen on the client end speaking to the server. Now for removing it inside of the application, you would need to create a screen for the user to disable the functionality. Lastly, the exceptions raised that I am talking about in the code will let you know if the user has uninstalled the application, etc.

  16. Mark Peters permalink

    Mike,

    Well, I’m really referring to the development required to implement the functionality specific to a particular application server, not what is required to implement communication with C2DM.

    For instance, on the application server I’ll need to maintain a database of all users (MySQL?) and the relevant reg info for each, home directories for each user to contain their data, scripts (PHP?) to handle data updates from users, and in the long run, web pages to give users off-device access to their accounts and data.

    As a novice in server-side development, I’m looking for recommendations on the best way to learn the required server-side development skills, e.g., books, articles, etc. Right now, just reading PHP code is like reading greek. ;-)

    Thanks,

    Mark Peters

  17. Hi,

    Thanks for all the code. I had just started to write a Zend C2DM extension to the GData API, but I thought I better Google first.

    Anyway, have you any plans of giving this code back to Zend? I’d love for it to be included there.

    thanks

  18. Rick permalink

    I was under the impression that you had to send the registration id back from the device to the third party server in the C2DMReceiver class. But I do not see where that is happening. So how does it know?

  19. getCollapseKey is duplicated in Zend_Service_Google_C2dm_Message

  20. I’m a novice to PHP and anything on the server side of things. I’ve got C2DM working and have tested it using cURL on a windows 7 x64 machine using this tutorial:

    http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html#tutorial_curl

    My app will only be sending out messages to around 10 to 15 devices and will not be open to the public. I don’t have my own website but could probably set up some type of server at home.

    If you have any suggestions on setting up the server side of things from scratch I’d appreciate it. Or if you could point me in the right direction.

  21. Dustin Evans permalink

    I’ve got an II6 server running PHP and ZEND. I’ve made a project using the zf.bat and then copied and pasted the above code into the C2DM project. I have no idea how to “launch” the code. I’ve tried mysite.com/C2DM and nothing as well as mysite.com/C2DM/application/workers/C2dmWorker.php and all I get is the .php code from the above tutorial. Any suggestions?

  22. Antonio permalink

    Hi, Mike, thanks for share your code. I’m a little bit newbbie and don’t understand well this part:

    $message->setId(time());
    $message->setToken(‘ABCDEF0123456789′);
    $message->setData(array(
    ‘foo’ => ‘bar’,
    ‘bar’ => ‘foo’,
    ));

    I think that ‘ABC…’ is the registration_id of my device, but what is SetId(time())? And on the setData can i put somethng like SetData(array(‘Hello world’))? Thanks

  23. $message->setId(time()) is just creating a collapse key; another way to do this is to actually take the payload and set it to a crc32 to ensure that it is the same message.
    $message->setData(array(…)); are key value pairs; these come to the application as data.key = value (example in the above would be data.foo = bar and data.bar = foo). See http://code.google.com/android/c2dm/#push for the field details.
    the ABCDEF… is correct; that is the registration id.

  24. This would require a gearman worker; you would need PHP & Zend Framework (note the framework portion). It is not a navigable application but a worker for gearman. You are welcome to create a front-end for it by using a form and testing the posting by using a simple message handler; if you need help with this let me know.

  25. It all really depends on what you are doing and your requirements as to how “real-time” the push messages must be sent. You can quickly and easily send these out through a simple cronjob (which would not require the guts of what I’ve put above). I will look at creating a simple app for people at some point here to explain the usage a bit more.

  26. Antonio permalink

    Hi Mike, i get the error ‘The message is not valid’ when trying to send a message to the device. I’ve sending this message:

    $message = new Zend_Mobile_Push_Message_C2dm();
    $message->setId(time());
    $message->setToken(‘a2sd123g3_______my_id’);
    $message->setData(array(‘message’ => ‘hello’));

    Is it well formed? Thanks.

  27. Hello Antonio,

    The message does look well-formed. However, in order for it to fail validation in this case it does a quick check to ensure the id and token are a string. In the case of setId you are setting it to time() which is an int. This should be allowed and I will change the check to check for a scalar instead.

    Regards,

    Mike

  28. This has now been fixed and pushed into the git repository. Thanks for letting me know about this.

Trackbacks & Pingbacks

  1. Android C2DM with PHP and Zend Framework | Mike Willbanks | marcusjpotter

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS