Push Service Library

The Push Service allows a push-enabled application to send requests to the Push Proxy Gateway (PPG). The PPG provides a set of services that enables interaction between content providers and push-enabled applications.

Overview

The Push Service supports both sending requests to and receiving responses from the PPG. Applications that receive response messages while they are running are notified via the push_callback_t function.

The following commands are supported by the Push Service: The application can send an application level acknowledgment that indicates whether the application accepted or rejected the pushed content (push_service_accept_push() or push_service_reject_push()).
The following steps show the general flow when you use the Push Service:
  1. Initialize BlackBerry Platform Services (BPS) (for more information about BPS, see bps/bps.h)

  2. Establish Interaction with the Push Notification Service (PNS) Agent. The PNS Agent is a software component that maintains a connection with the PPG, and forwards push notifications that it receives from the PPG to the appropriate instance of the push-enabled application.

  3. Request navigator events and listen for events

  4. Receive Push messages

  5. Shutdown your application

  6. Shutdown BPS

The code below is a sample main() function that demonstrates this flow. To see push-specific code snippets and explanations, go to the section mentioned in the comments in the code. For example, where it says:
 // ********************************
 // See section Establish Interaction with the PNS Agent
 // ********************************
Go to the section below titled "Establish interaction with the PNS Agent" to see the code snippets and explanations.
 int main(int argc, char *argv[])
 {
   // Initialize BPS
   if (bps_initialize() == BPS_FAILURE){
     printf("Failed to initialize bps\n");
     return EXIT_FAILURE;
   }

   // ********************************
   // See section Establish Interaction with the PNS Agent
   // ********************************

   // To keep things simple just show a blue screen
   show_screen(0xff0000ff);

   // Signal BPS library that navigator and screen events will be 
   // requested
   screen_request_events(screen_ctx);
   navigator_request_events(0);

   while (!shutdown) {
     // Handle user input
     handle_event();
   }

   // ********************************
   // See section Application shutdown
   // ********************************

   // Shutdown BPS
   screen_stop_events(screen_ctx);
   bps_shutdown();
   screen_destroy_window(screen_win);
   screen_destroy_context(screen_ctx);

   printf("Main Loop exit.\n");
   return EXIT_SUCCESS;
 }

void handle_event()
{
  bps_event_t *event = NULL;
  int rc = bps_get_event(&event, -1);

   if (rc != BPS_SUCCESS) {
     ERR("BPS get event failed.");
   }
   else if (event) {
     int domain = bps_event_get_domain(event);
     printf("Got domain[%d]\n", domain);
     if (domain == navigator_get_domain()) {
       printf("Received Navigator event\n");

       // ********************************
       // See section Receiving a Push Message
       // ********************************
       handle_navigator_event(event);
     }
   }
 }

Establish interaction with the PNS Agent

Create a push_service_t structure to invoke the APIs from the Push Service library. Obtain the Push Service file descriptor using push_service_get_fd(), and add it to the list of file descriptors monitored by BPS using bps_add_fd(). The I/O handler is called by BPS when activity is detected on the Push Service file descriptor.

 int main(int argc, char *argv[])
 {
   // .. omitted for brevity

   push_service_t *ps = NULL;
   int rc = push_service_initialize(&ps);
   if (rc == PUSH_FAILURE || (ps == NULL)) {
     printf("start_push_service: failed init push_service  
             errno[%d] error[%s]\n", errno, 
             strerror(errno));
     return EXIT_FAILURE;
   }

   int push_pps_fd = push_service_get_fd(ps);
   if (push_pps_fd == PUSH_INVALID_PPS_FILE_DESCRIPTOR) {
     printf("invalid push pps file descriptor\n");
     return EXIT_FAILURE;
   }

   // Add the Push Service file descriptor to a list of 
   // descriptors monitored by BPS.
   if (bps_add_fd(pushPpsFd, BPS_IO_INPUT, &push_io_handler, ps)  
                == BPS_FAILURE) {
     printf("Failed to add push file descriptor %d to bps\n", 
            pushPpsFd);
     return EXIT_FAILURE;
   }

   // .. omitted for brevity
 }

 // Push io handler
 int push_io_handler(int fd, int io_events, void* opaque) {
 int rc = BPS_FAILURE;
 push_service_t* ps = (push_service_t*)opaque;

    if (ps != NULL) {
      int old_fd = push_service_get_fd();
      printf("calling push service to process the incoming 
                        PPS message\n");
      if (push_service_process_msg(ps) == PUSH_SUCCESS) {
        int new_fd = push_service_get_fd();

        // Push connection has been closed.
        // Need to remove the Push Service file descriptor from 
                               // the list monitored by BPS.
        if (new_fd == PUSH_INVALID_PPS_FILE_DESCRIPTOR) {
          if (bps_remove_fd(old_fd) == BPS_FAILURE) {
            printf("Failed to remove PPS file descriptor\n");
          }
        }
        rc = BPS_SUCCESS;
         rc = BPS_SUCCESS;
      }
    }

    return rc;
  }
A push_service_t structure interacts with the PNS Agent through a session. You need to pass the Provider Application ID and the Invoke Target Key to create the session. The Application ID is the unique identifier of the client application that you received from BlackBerry when you registered to use the Push Service. The Target Key identifies the client application as the target of push content, and it's a value that you generate.
 rc = push_service_set_provider_application_id(ps, provider_app_id);
 if (rc == PUSH_FAILURE) {
   printf("push_service_set_provider_application_id: errno[%d]"
          "error[%s]\n", errno, strerror(errno));
   return false;
 }
 rc = push_service_set_target_key(ps, target_key);
 if (rc == PUSH_FAILURE) {
   printf("push_service_set_target_key: errno[%d] error[%s]\n",
           errno, strerror(errno));
   return false;
 }

 rc = push_service_create_session(ps, on_create_session_complete);
 if (rc == PUSH_FAILURE) {
   printf("push_service_create_session: errno[%d] error[%s]\n",
           errno, strerror(errno));
   return false;
 }

Calls to create sessions are asynchronous. The provided on_create_session_complete() callback function will be invoked when the push_service_create_session() request is complete.

 void on_create_session_complete(push_service_t* ps, int status_code) {
   printf("create_session_complete called status_code[%d]", 
           status_code);

   if (status_code == PUSH_NO_ERR) {
     rc = push_service_set_ppg_url(ps, ppg_url);
     if (rc == PUSH_SUCCESS) {
       rc = push_service_create_channel(ps, 
              on_create_channel_complete, 
              create_channel_on_push_transport_ready);
     }
     else {
       printf("push_service_set_ppg_url: errno[%d] error[%s]",  
               errno, strerror(errno));
     }
  if (status_code == PUSH_NO_ERR) {
     rc = push_service_set_ppg_url(ps, ppg_url);
     if (rc == PUSH_SUCCESS) {
        rc = push_service_create_channel(ps, on_create_channel_complete, create_channel_on_push_transport_ready);
     }
     else {
        printf("push_service_set_ppg_url: errno[%d] error[%s]", errno, strerror(errno));
     }

 }
}

Create a channel with the PPG

You need to create a channel with the PPG so that your applicaton and the device it runs on are enlisted to receive content whenever the PPG sends content. The request to create a channel is sent through the PNS Agent. You need the PPG URL to invoke the call to create a channel.

The callback functions cannot be NULL. The create_channel_on_push_transport_ready() callback function is used to handle the scenario where the push_service_create_channel() request failed with a PUSH_ERR_TRANSPORT_FAILURE (10103) or PUSH_ERR_PPG_SERVER_ERROR (10110) status code.

 void on_create_channel_complete(push_service_t* ps, 
      int status_code) {
   printf("on_create_channel_complete called statusCode[%d]", 
          status_code);
 }

 void create_channel_on_push_transport_ready(push_service_t* ps, int 
        status_code) {
   push_service_create_channel(ps, 
        on_create_channel_complete, 
        create_channel_on_push_transport_ready);
 }

 // ...
 
 rc = push_service_set_ppg_url(ps, ppg_url);
   if (rc == PUSH_SUCCESS) {
     rc = push_service_create_channel(ps, on_create_channel_complete, 
                create_channel_on_push_transport_ready);
 }
 else {
   printf("push_service_set_ppg_url: errno[%d] error[%s]",
          errno, strerror(errno));
 }

Application shutdown

On application shutdown, you should explicitly deallocate any memory allocated for a given push_service_t structure using push_service_cleanup(). You also need to remove the Push Service file descriptor from the list monitored by BPS using bps_remove_fd().

 rc = push_service_cleanup(ps);
 if (rc == PUSH_FAILURE) {
   printf("push_service_cleanup: errno[%d] error[%s]\n",
          errno, strerror(errno));
 }

 if (bps_remove_fd(pushPpsFd) == BPS_FAILURE){
   printf("Failed to remove PPS file descriptor\n");
 }

Receiving a Push Message

To receive a push message you need to listen for a navigator invoke event in your main event loop. The action value of this event must be PUSH_INVOCATION_ACTION. If these criteria are met, then you can extract the invoke data into a push_payload_t structure using push_payload_create() and push_payload_set_payload(). Before using the structure, check that the data in the structure is valid using push_payload_is_valid(). After you finish using the push_payload_t structure, you should explicitly deallocate any memory given to push_payload_t using push_payload_destroy().

 void handle_navigator_event(bps_event_t *event) {
   int event_type = bps_event_get_code(event);
   printf("received event type [%d]\n", event_type);

   switch (event_type) {
   case NAVIGATOR_EXIT:
     printf("NAVIGATOR Exit event\n");
     shutdown = true;
     break;
   case NAVIGATOR_INVOKE_TARGET: {
     // Our handler was invoked
     const navigator_invoke_invocation_t *invoke = 
                     navigator_invoke_event_get_invocation(event);
     if(invoke) {

       const char *action =
             navigator_invoke_invocation_get_action(invoke);
       printf("Got invoke: action='%s'\n", action ? action : "NULL");

       if (strcmp(action,PUSH_INVOCATION_ACTION) == 0){

         // get the jsonData from the invoke object and
         // put it into a push_payload_t struct
         const unsigned char* raw_invoke_data =
               (unsigned char*) 
                navigator_invoke_invocation_get_data(invoke);
         int invoke_data_len = 
               navigator_invoke_invocation_get_data_length(invoke);

         printf("Creating push_payload_t from raw_invoke_data\n");
         push_payload_t* push_payload;
         if(push_payload_create(&push_payload) == PUSH_FAILURE){
           printf("failed to create push_payload. errno[%d]\n", 
                  errno);
           return FAILURE;
         }

         if (push_payload_set_payload(push_payload,
                                      raw_invoke_data,
                                      invoke_data_len) ==
               PUSH_SUCCESS && push_payload_is_valid(push_payload)){
           printf("push_payload_t is valid\n");
           process_push_payload(push_payload);
         } else {
           printf("push_payload_t is NOT valid\n");
         }

         // cleanup
         push_payload_destroy(push_payload);
       }
     }
     break;
   }

   default:
     break;
   }
 }

Processing a Push Payload Structure

Here is an example of how to retrieve the data from a push_payload_t structure, and how to loop through all of the headers.

void process_push_payload(const push_payload_t* payload)
{
   const unsigned char* data = push_payload_get_data(payload);
   size_t data_length = push_payload_get_data_length(payload);

   printf("data length : [%d]\n", data_length);

   size_t headers_length = push_payload_get_headers_length(payload);

   for(int i=0; i <headers_length;i++){

     const push_header_t* header =
                          push_payload_get_header(payload,i);

     if (header){
       const char* header_name = push_header_get_name(header);
       const char* header_value = push_header_get_value(header);

       if (header_name){
         printf("Header name: [%s]\n", header_name);
       }

       if (header_value){
         printf("Header value: [%s]\n", header_value);
       }
     }
   }
   }
}

Detecting a changed SIM card

When the SIM card is changed on a device, the PNS Agent automatically destroys the channel. You can implement a callback function to handle the SIM change.

A call to push_service_create_channel() is recommended.

 void on_sim_change(push_service_t* ps) {
   push_service_create_channel(ps, on_create_channel_complete, 
                create_channel_on_push_transport_ready);
 }

Detecting when the PNS Agent connection is closed

If the connection to the PNS Agent has been closed, the application or service needs to re-establish the connection with the PNS Agent by calling push_service_get_fd() periodically until push_service_get_fd() returns a valid file descriptor. You can implement a callback function to handle when the connection closes.

 push_service_set_connection_close_callback(ps, on_connection_closed);

Calling push_service_get_fd() in a backoff timer is recommended.

 void on_connection_closed(push_service_t* ps) {
   // Start backoff timer which periodically calls  
   // push_service_get_fd() until push_service_get_fd() returns a 
   // valid file descriptor.
 }

Last modified: 2013-12-23

comments powered by Disqus