Creating a native service for Tizen wearables - part 3: circular UI and shared preferences

Introduction

This article is a third part of the “Create a native service for Tizen wearables” series. Before reading this article you should have read the part 1, which explains a simple service running in the background, and the part 2, which shows how to make a simple wearable UI application combined into one package with the service.

In the third part you will learn how to create a simple circular UI on a wearable device and how to exchange preferences between the launcher application and the service.

Prerequisites

Before reading this article please read previous parts of the cycle:

https://developer.tizen.org/community/tip-tech/creating-native-service-tizen-wearables-part-1

https://developer.tizen.org/community/tip-tech/creating-native-service-tizen-wearables-part-2-service-launcher

Understanding the previous subjects is necessary to get on with this article.

As in this article we introduce EFL genlist and circle UI APIs, you’d better be familiarized with their basics. The following tutorials should be helpful:

https://developer.tizen.org/development/ui-practices/native-application/efl/ui-components/wearable-ui-components/genlist

https://developer.tizen.org/development/ui-practices/native-application/efl/ui-components/wearable-ui-components/circle-genlist

Also we will use shared preferences between the launcher and the service. You can have a look on the following tutorial:

https://developer.tizen.org/development/api-tutorials/native-application/application-framework/application/preference

 

Preparation steps and code arrangement

The attached code is a complete source code of the created service and the accompanying app. You can import them to your SDK and after creating the Combined Package (see “Creating a native service for Tizen wearables – part 2” article) they should be ready to install and use when you have a properly set wearable device.

If you had been testing the example source code from “Creating a native service for Tizen wearables – part 2” article, you probably still have the MyService & MyServiceLauncher applications installed on the device. You can follow the steps described in the article and modify those old versions of the Service and UI applications on your own.

Only the parts of the code that have been added to the previous Service and UI applications (as they were in “Creating a native service for Tizen wearables – part 2” article) are discussed in this article.

 

Steps to do

Step 1. Adding circular UI to the MyServiceLauncher application

  1. Creating the circular UI genlist
  2. Adding circular events handling

Step 2. "Hidden mode" shared preference implementation

  1. MyServiceLauncher: Adding a shared preference for the hidden mode
  2. MyServiceLauncher: Adding a checkbox for the hidden mode setting
  3. MyService: Verifying the hidden mode preference setting

Step 1: Adding circular UI to the MyServiceLauncher application

  1. Creating the circular UI genlist

In the previous article we used simple elm_list to create a simple scrollable menu.

However, if we want to create a UI more visually attractive and compatible with the default circle smartwatch appearance we should use EFL UI extensions for circle surface and a genlist.

Genlist (Generic list) is a container widget which is recommended especially for building advanced lists - the ones containing various types of content or the ones with a large number of elements. In our case we will use genlist because its 3D appearance it is more visually attractive on a wearable device than the appearance of a flat simple list. 

Circle UI appearance for My Service Launcher application Circle UI appearance for My Service Launcher application - scrolling result Circle UI appearance for My Service Launcher application - scrolling result

Figure 1: The application menu in Circle UI version

That’s why in the appdata_s structure we add the circle genlist handle and the circle surface handle and we declare three genlist item classes: the title class for the list header, the line class for a regular list item and the padding class which creates a margin to the bottom of the list.

typedef struct appdata {   
    //...
    
    // We replace a list from the previous version of the application with a genlist
    Evas_Object *genlist;
    
    // We add the circle genlist handle and the circle surface handle
    Evas_Object *circle_genlist;
    Eext_Circle_Surface *surface;

    // A class for genlist regular item
    Elm_Genlist_Item_Class genlist_line_class;
    
    // A class for genlist header
    Elm_Genlist_Item_Class genlist_title_class;
    
    // A class for genlist ending margin
    Elm_Genlist_Item_Class genlist_padding_class;

    //...
} appdata_s;

 

According to the genlist requirements we provide the simple text fetching function which will be used in the genlist regular item. This function just will fill the genlist item text part with the text provided under data pointer.

// The text fetching function that will be used for genlist_line_class
static char *plain_label_get(void *data, Evas_Object *obj, const char *part)
{
    char* label = (char*)data;
    return strdup(label);
}

 

Now we move to the create_base_gui() function, where the whole user interface is being set up. We add the conformant of our UI to the previously declared circle surface.

//in create_base_gui() function body:

    /* Circle surface */
    ad->surface = eext_circle_surface_conformant_add(ad->conform);

Then we start to define genlist item classes. As we said before, we need three genlist item classes: one class for the list header (we called it genlist_title_class), one for regular list itema (we called it genlist_line_class), and one for padding at the end of the list (we called it genlist_padding_class). We use standard styles provided with Elementary for those classes and use previously defined plain_label_get function for fetching labels for genlist items.

/* Genlist and genlist items classes */
    ad->genlist = elm_genlist_add(ad->conform);

    ad->genlist_line_class.item_style = "1text";
    ad->genlist_line_class.func.text_get = plain_label_get;
    ad->genlist_line_class.func.content_get = NULL;
    ad->genlist_line_class.func.state_get = NULL;
    ad->genlist_line_class.func.del = NULL;

    ad->genlist_title_class.item_style = "title";
    ad->genlist_title_class.func.text_get = plain_label_get;
    ad->genlist_title_class.func.content_get = NULL;
    ad->genlist_title_class.func.state_get = NULL;
    ad->genlist_title_class.func.del = NULL;

    ad->genlist_padding_class.item_style = "padding";

 

Finally, we fill our genlist menu with the same items as in the previous version of the application:

    elm_genlist_item_append(ad->genlist, 
                            &(ad->genlist_title_class), 
                            (void*)"My Service Launcher", 
                            NULL, 
                            ELM_GENLIST_ITEM_NONE, 
                            NULL, 
                            NULL);
    elm_genlist_item_append(ad->genlist, 
                            &(ad->genlist_line_class), 
                            (void*)"Launch service", 
                            NULL, 
                            ELM_GENLIST_ITEM_NONE, 
                            launch_service_cb, 
                            (void*)ad);
    elm_genlist_item_append(ad->genlist, 
                            &(ad->genlist_line_class), 
                            (void*)"Stop service", 
                            NULL, 
                            ELM_GENLIST_ITEM_NONE, 
                            stop_service_cb, 
                            NULL);
    elm_genlist_item_append(ad->genlist, 
                            &(ad->genlist_line_class), 
                            (void*)"Close", 
                            NULL, 
                            ELM_GENLIST_ITEM_NONE, 
                            close_app_cb,
                            NULL);
    elm_genlist_item_append(ad->genlist, 
                            &(ad->genlist_padding_class), 
                            NULL, 
                            NULL, 
                            ELM_GENLIST_ITEM_NONE, 
                            NULL, 
                            NULL);

    elm_object_content_set(ad->conform, ad->genlist);
    evas_object_show(ad->genlist);

 

  1. Adding circular events handling

Now we want to connect our genlist with circular UI events such as a rotary scrolling event:

//in create_base_gui() function body:

    /* Circle genlist extension */
    ad->circle_genlist = eext_circle_object_genlist_add(ad->genlist, ad->surface);
    eext_circle_object_genlist_scroller_policy_set(ad->circle_genlist, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO);
    eext_rotary_object_event_activated_set(ad->circle_genlist, EINA_TRUE);

At this stage we have fully operational circle UI for our application.

 

Step 2: "Hidden mode" shared preference implementation

The second improvement done to the service described in this article, will be using shared preferences between the launcher application and the service application. We will show you how to do this on the “hidden mode” implementation example.

Hidden mode in MyService application will be an option in which when the sensor event occurs, the Launcher UI is not shown, only the notification sound is played.

To do this we will introduce Preference API from the Application Framework. Preference gives you a possibility to store key-value pairs for your application settings.  Those preferences are kept after the application is closed and when the device is shut down. Moreover, the preferences can be shared between applications that are combined into one package.

  1. MyServiceLauncher: Adding a shared preference for the hidden mode

First of all, we must include the app_preference.h header.

#include <app_preference.h>

Also we can define our preference key as a macro (both in the launcher application and in the service) to avoid mistake when checking it repeatedly:

#define PREF_KEY_HIDDEN_MODE "hidden_mode"    // the key name for the hidden mode setting in preferences

Then we must check if the preference was already set. This is important in the first application run. We must set up the default setting if the key was not created yet. 

//in app_create() function body:

    bool exists = false;
    if (preference_is_existing(PREF_KEY_HIDDEN_MODE, &exists) == PREFERENCE_ERROR_NONE)
    {
        if (exists == false)
        {
            if (preference_set_boolean(PREF_KEY_HIDDEN_MODE, false) == PREFERENCE_ERROR_NONE)
            {
                dlog_print(DLOG_INFO, LOG_TAG, "Hidden mode setting was not present, setting to default false value...");
            }
            else
            {
                dlog_print(DLOG_ERROR, LOG_TAG, "Error setting preference for hidden mode!");
            }
        }
        // If the preference already exists, everything is all right - doing nothing...
    }
    else
    {
        dlog_print(DLOG_ERROR, LOG_TAG, "Error setting preference for hidden mode!");
    }
  1. MyServiceLauncher: Adding a checkbox for the hidden mode setting

Now we must add a setting option to our UI. To do this, we must add one more field to our genlist.

The application UI with &quot;Hidden mode&quot; option

Figure 2: The application UI with "Hidden mode" option

The field will contain a label and a checkbox, so we create another genlist item class: 

// in appdata_s struct definition:

Elm_Genlist_Item_Class genlist_checkbox_class;

As before, we create a text fetching function for the text field of the class:

// The text fetching function that will be used for genlist_checkbox_class
static char *checkbox_label_get(void *data, Evas_Object *obj, const char *part)
{
    return strdup("Hidden mode");
}

However apart of that, we have to add also the content fetching function. We add a checkbox to the “elm_icon” field in the genlist item. Then we set the checkbox to reflect the current hidden mode setting (remember to define the PREF_KEY_HIDDEN_MODE macro as the same value as in MyServiceLauncher app!) . Finally, we add a callback that changes the mode when checkbox setting is changed by a user.

static void set_mode_cb(void *data, Evas_Object *obj, void *event_info);

static Evas_Object *checkbox_content_get(void *data, Evas_Object *obj, const char *part)
{
    if (!strcmp(part, "elm.icon"))
    {
        // In a part named "elm_icon" we will place the checkbox widget.
        Evas_Object *checkbox = elm_check_add(obj);
        elm_object_style_set(checkbox, "on&off");

        // We don't want to propagate checkbox click event to the genlist item:
        evas_object_propagate_events_set(checkbox, EINA_FALSE);

        // Displaying the current hidden mode setting
        bool mode = false;
        if ((preference_get_boolean(PREF_KEY_HIDDEN_MODE, &mode) == PREFERENCE_ERROR_NONE)
            && (mode == true))
        {
            dlog_print(DLOG_INFO, LOG_TAG, "Hidden mode is true!");
            elm_check_state_set(checkbox, EINA_TRUE);
        }
        else
        {
            dlog_print(DLOG_INFO, LOG_TAG, "Hidden mode is false!");
            elm_check_state_set(checkbox, EINA_FALSE);
        }
        evas_object_smart_callback_add(checkbox, "changed", set_mode_cb, NULL);
        return checkbox;
    }
    // We do nothing for any other part.
    else return NULL;
}

Here is the callback definition. We use Preference API to store the current hidden mode setting. 

static void set_mode_cb(void *data, Evas_Object *obj, void *event_info)
{
    bool mode = false;
    if ((preference_get_boolean(PREF_KEY_HIDDEN_MODE, &mode) == PREFERENCE_ERROR_NONE)
        && (mode == true))
    {
        if (preference_set_boolean(PREF_KEY_HIDDEN_MODE, false) == PREFERENCE_ERROR_NONE)
        {	
            dlog_print(DLOG_INFO, LOG_TAG, "Hidden mode was true! Setting to false");
        }
        else
        {
            dlog_print(DLOG_INFO, LOG_TAG, "Setting hidden mode failed!");
        }
    }
    else
    {
        if (preference_set_boolean(PREF_KEY_HIDDEN_MODE, true) == PREFERENCE_ERROR_NONE)
        {
            dlog_print(DLOG_INFO, LOG_TAG, "Hidden mode was false! Setting to true");
        }
        else
        {
            dlog_print(DLOG_INFO, LOG_TAG, "Setting hidden mode failed!");
        }
    }
}

As it was described in the Step 1, we construct the genlist item class for the hidden mode setting field:

// in create_base_gui() function body:

    // The genlist item class for the field with a label and a checkbox to the right.
    ad->genlist_checkbox_class.item_style = "1text.1icon.1";
    ad->genlist_checkbox_class.func.text_get = checkbox_label_get;
    ad->genlist_checkbox_class.func.content_get = checkbox_content_get;
    ad->genlist_checkbox_class.func.state_get = NULL;
    ad->genlist_checkbox_class.func.del = NULL;

    elm_genlist_item_append(ad->genlist, &(ad->genlist_checkbox_class),(void*)ad, NULL, ELM_GENLIST_ITEM_NONE, NULL, (void*)ad)
  1. MyService: Verifying the hidden mode preference setting

Now, when we implemented setting the preference in the Service Launcher UI, all that we need to do in the Service application is to check the preference setting when the sensor event occurs.

We include the app_preference.h header in the MyService application as well: 

#include <app_preference.h>

Then, we modify the sensor_event_callback() to show MyServiceLauncher application only when the hidden mode is set to off. 

// In the sensor_event_callback() function body:

// ... When sensor reading criteria are met and the sound is played,
// we launch the launcher UI application only when the hidden mode setting is off.

bool mode = false;
if ((preference_get_boolean(PREF_KEY_HIDDEN_MODE, &mode) == PREFERENCE_ERROR_NONE)
    && (mode == false))
{
    // We launch MyServiceLauncher as in the old version of MyService...
    // The code below doesn't differ from the version in the previous article.
    app_control_h app_control;
    if (app_control_create(&app_control)== APP_CONTROL_ERROR_NONE)
    {
        //Setting an app ID.
        if (app_control_set_app_id(app_control, MYSERVICELAUNCHER_APP_ID) == APP_CONTROL_ERROR_NONE)
        {
            if(app_control_send_launch_request(app_control, NULL, NULL) == APP_CONTROL_ERROR_NONE)
            {
                dlog_print(DLOG_INFO, LOG_TAG, "App launch request sent!");
            }
        }
        if (app_control_destroy(app_control) == APP_CONTROL_ERROR_NONE)
        {
            dlog_print(DLOG_INFO, LOG_TAG, "App control destroyed.");
        }
    }
}

// If hidden mode is on, we do not launch the UI application and play sound only.

Summary

Now you know how to create a genlist-based circular menu and how to share common preferences between the UI application and the background application. This article was the third one of a “Create a native service for Tizen wearables” series which covered most important issues concerning creation of a simple service for Tizen wearable devices.

File attachments: