Hybrid Service Sample Overview

Mobile native

The HybridServiceApp sample application demonstrates how you can communicate with a Tizen Web application (HybridWebApp) to manage the timer for the application.

The HybridServiceApp is a service application that has no user interface. It creates the timer, and starts and stops it according to the requests received from the HybridWebApp application.

As HybridServiceApp is a service, call the service_app_main() function of the Service App API to create its instance. In addition, set the service callbacks, such as service_app_event_callback_s.

int app_run(app_data *app, int argc, char **argv)
{
   RETVM_IF(!app, -1, "Application data is NULL");

   app_event_handler_h lmh;
   app_event_handler_h lbh;
   service_app_add_event_handler(&lmh, APP_EVENT_LOW_MEMORY, _on_low_memory_cb, app);
   service_app_add_event_handler(&lbh, APP_EVENT_LOW_BATTERY, _on_low_battery_cb, app);

   service_app_lifecycle_callback_s cbs =
   {
      .create = _on_create_cb,
      .terminate = _on_terminate_cb
   };

   return service_app_main(argc, argv, &cbs, app);
}

To destroy the service, call the service_app_exit() function.

Declare the application type as service in the manifest XML file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns="http://tizen.org/ns/packages" package="org.tizen.hybridsvc" version="1.0.0">
   <service-application appid="org.tizen.hybridsvc" exec="hybridsvc" type="capp" multiple="false" taskmanage="false" nodisplay="true">
      <label>HybridSvc</label>
   </service-application>
</manifest>

The hybrid Web application must launch the service application by its application ID. Before running the hybrid Web application in the project folder, JavaScript must open the main.js file, find the gServiceAppId initialization variable, and change its value on token from the HybridServiceApp manifest file appid value (appid="org.tizen.hybridsvc").

Implementation

Communication with the Web application is set using the proxy_client functionality that uses the Message Port API.

The proxy_client_create() function creates a proxy client instance for storing the main communication information as a local message port ID, remote application name, and its message port name.

proxy_client *proxy_client_create()
{
   proxy_client *const proxy = calloc(1 , sizeof(proxy_client));

   return proxy;
}

To register the message port on the service side, use the proxy_client_register_port() function.

int proxy_client_register_port(proxy_client *proxy_cl, const char *const port_name)
{
   int result = SVC_RES_FAIL;

   RETVM_IF(!proxy_cl, result, "Proxy client is NULL");
   RETVM_IF(!port_name, result, "Message port name is NULL");

   int temp_id = message_port_register_local_port(port_name, _on_message_received_cb);
   if (temp_id < 0)
   {
      _proxy_client_convert_msg_port_result(temp_id);
      ERR("Failed to register local message port");
      proxy_cl->local_port_id = 0;

      return result;
   }

   DBG("Message port %s registered with id: %d", port_name, temp_id);
   proxy_cl->local_port_id = temp_id;

   return SVC_RES_OK;
}

The proxy_client_register_msg_receive_callback() function is needed to register the internal proxy_client callback data and a function that is called when the registered message port receives data from the Web application.

int proxy_client_register_msg_receive_callback(proxy_client *proxy_cl,
                                               proxy_client_callback_func callback_func,
                                               void *data)
{
   RETVM_IF(!proxy_cl, SVC_RES_FAIL, "Proxy client is NULL");

   callback_data.proxy_cl = proxy_cl;
   callback_data.func = callback_func;
   callback_data.data = data;

   return SVC_RES_OK;
}

To send messages by the proxy_client callback, use the proxy_client_send_message() function. Use the message_port_send_message_with_local_port() function of the Message Port API.

int proxy_client_send_message(proxy_client *proxy_cl, bundle *const msg)
{
   int result = SVC_RES_FAIL;

   RETVM_IF(!proxy_cl, result , "Proxy client is NULL");
   RETVM_IF(!(proxy_cl->local_port_id), result, "Message port is not registered");
   RETVM_IF(!(proxy_cl->remote_app_name), result, "Remote application name is not registered");
   RETVM_IF(!(proxy_cl->remote_port_name), result, "Remote message port is not registered");

   result = _proxy_client_convert_msg_port_result(message_port_send_message_with_local_port(proxy_cl->remote_app_name,
                                                                                            proxy_cl->remote_port_name,
                                                                                            msg,
                                                                                            proxy_cl->local_port_id));

   RETVM_IF(result != SVC_RES_OK, result, "Failed to send bidirectional message to: %s:%s",
            proxy_cl->remote_app_name,
            proxy_cl->remote_port_name);

   DBG("Message successfully send to: %s:%s", proxy_cl->remote_app_name, proxy_cl->remote_port_name);

   return result;
}

The private _on_message_received_cb() function is a registered message port callback function. This function is called if an application receives information from the Web application. The proxy_client callback function is called in this method.

static void _on_message_received_cb(int port_id, const char *rem_app_name, const char *rem_port_name,
                                    bool trusted_message, bundle *rec_msg)
{
   DBG("Received message from port %d", port_id);

   if (!callback_data.proxy_cl)
   {
      return;
   }

   if (port_id != callback_data.proxy_cl->local_port_id)
   {
      ERR("Receive message by unknown port id = %d", port_id);

      return;
   }

   int res = _proxy_client_set_remote_data(callback_data.proxy_cl, rem_app_name, rem_port_name);
   RETM_IF(res != SVC_RES_OK ,"Failed to set remote data to message port");

   if (callback_data.func)
   {
      res = callback_data.func(callback_data.data, rec_msg);
      RETM_IF(res != SVC_RES_OK ,"Message port callback function failed");
   }
   else
   {
      DBG("Message port callback function not set");
   }
}

When the proxy_client callback function is called, the service decides what response must be sent to the Web application (the _app_process_received_message() function) and what action must be taken on the service side (the _app_execute_operation() function).

For example, if a message with the start command is received, the service must run a timer that periodically notifies the Web application, and on receiving the stop command, the timer must be stopped. After that, the service sends a response to the Web application.

static int _on_proxy_client_msg_received_cb(void *data, bundle *const rec_msg)
{
   int result = SVC_RES_FAIL;
   RETVM_IF(!data, result, "Data is NULL");

   app_data *app = data;
   req_operation req_operation = REQ_OPER_NONE;

   bundle *resp_msg = bundle_create();
   RETVM_IF(!resp_msg, result, "Failed to create bundle");

   result = _app_process_received_message(rec_msg, resp_msg, &req_operation);
   if (result != SVC_RES_OK)
   {
      ERR("Failed to generate response bundle");
      bundle_free(resp_msg);

      return result;
   }

   result = _app_execute_operation(app, req_operation);
   if (result == SVC_RES_OK)
   {
      result = _app_send_response(app, resp_msg);
      if (result != SVC_RES_OK)
      {
         ERR("Failed to send message to remote application");
      }
   }
   else
   {
      ERR("Failed to execute operation");
   }
   bundle_free(resp_msg);

   return result;
}

The timer is realized on a separate thread. When starting the application, the thread instance is created and the thread function is locked by the waiting signal to start. After the start signal is received, it starts to send notifications at 1 second intervals until the stop signal is received. The thread sleeps and waits for another notification.

static void* _app_timer_thread_func(void *data)
{
   RETVM_IF(!data, NULL, "Data is NULL");
   app_data *app = data;

   pthread_mutex_lock(&app->tmr_mutex);
   bool was_timedwait = false;

   while (app->run_svc)
   {
      if (app->run_timer)
      {
         if (was_timedwait)
         {
            RETVM_IF(_app_execute_operation(app, REQ_OPER_SEND_TIMER_EXP_MSG), NULL, "Execute operation failed");
         }
         else
         {
            was_timedwait = true;
         }
         INF("START WAITING BY TIMEOUT");
         struct timespec nowTs;
         clock_gettime(CLOCK_REALTIME, &nowTs);
         nowTs.tv_sec += TIMER_INTERVAL_RUN;
         pthread_cond_timedwait(&app->tmr_cond, &app->tmr_mutex, &nowTs);
         INF("STOP WAITING BY TIMEOUT");
      }
      else
      {
         was_timedwait = false;
         INF("START WAITING INFINIT");
         pthread_cond_wait(&app->tmr_cond, &app->tmr_mutex);
         INF("STOP WAITING INFINIT");
      }
   }
   pthread_mutex_unlock(&app->tmr_mutex);

   return NULL;
}