The ESM sensor adds support for user-provided context by leveraging mobile Experience Sampling Method (ESM). The ESM questionnaires can be triggered by context, time or on-demand, locally or remotely (within your study on AWARE’s dashboard). Although user-subjective, this sensor allows crowdsourcing information that is challenging to instrument with sensors.
Public functions
- ESM.queueESM( Context context, String esmJSON ): Adds ESMs (the string representation of JSON) to the queue.
Settings
- Aware_Preferences.STATUS_ESM: true or false to activate or deactivate ESM sensor.
AWARE provides a flexible ESM questionnaire building mechanism. Diverse ESM questions can be chained together to support a step-by-step questionnaire, and conditions can be used to create alternate questionnaire flows. The following elements are common to all ESMs:
- esm_type: a number identifying the kind of ESM
- esm_title: the title of the ESM (usually short, between 30-40 characters)
- esm_instructions: instructions to answer the ESM
- esm_submit: the label for the save button. If creating a queue, this will move to the next queued question
- esm_expiration_threshold: how long the user has to answer the question after it is displayed in the screen. The timer stops when the user touches the dialog. The ESM does not expire if set to 0 (zero) and will show a notification in the notification bar. If set to anything > 0, the ESM instantly prompts the user to answer the questionnaire, and will expire if the user has not touched the dialog.
- esm_notification_timeout: time after which the ESM notification is dismissed and the whole ESM queue expires (in case expiration threshold is set to 0).
- esm_trigger: a freeform string you can define for what could have triggered the ESM, useful for traceability and data analysis.
NOTE: Answering an ESM is never mandatory. If the user presses the Home or the Back button, the ESM is dismissed. We track the ESM statuses: new, dismissed, expired, branched or answered. An ESM does not have a default answer, i.e., if no answer as given, it will be blank.
- ESM.ACTION_AWARE_ESM_ANSWERED: broadcasted when the user answered an ESM question.
- ESM.ACTION_AWARE_ESM_DISMISSED: broadcasted when the user dismissed an ESM question.
- ESM.ACTION_AWARE_ESM_EXPIRED: broadcasted when the ESM expires.
- ESM.ACTION_AWARE_ESM_QUEUE_COMPLETE: broadcasted when there are no more ESM questions on the ESM queue.
- ESM.ACTION_AWARE_ESM_QUEUE_STARTED: broadcasted when the first ESM question from the queue of ESMs is presented to the user.
- ESM.ACTION_AWARE_QUEUE_ESM: received broadcast to add a new ESM to the ESM queue. One extra is required when issuing this broadcast:
- ESM.EXTRA_ESM: a string with a JSON array with the ESM definition, see previous section.
ESM Data
Contains the raw sensor data.
ESM_Data.CONTENT_URI
content://com.aware.provider.esm/esms
Table field | Field type | Description |
---|---|---|
_id | INTEGER | primary key, auto incremented |
timestamp | REAL | Unixtime milliseconds since 1970 |
device_id | TEXT | AWARE device UUID |
esm_json | TEXT | the ESM questionnaire JSON string (see below) |
esm_status | INTEGER | the status of the ESM (0-new, 1-dismissed, 2-answered, 3-expired, 4-visible, 5-branched) |
esm_expiration_threshold | INTEGER | ESM expiration threshold (in seconds) |
esm_notification_timeout | INTEGER | ESM notification timeout (in seconds) |
double_esm_user_answer_timestamp | REAL | time instance of user’s answer |
esm_user_answer | TEXT | the user’s answer |
esm_trigger | TEXT | a string with what triggered the ESM |
ESMs in practice
We have revamped how we build and queue ESMs to make it more user friendly and less error prone (moving away from having to write JSON objects!). To create an ESM queue, we now use the Factory software design pattern. Here are some examples.
Free text
This ESM allows the user to provide free text input as context. This can be leveraged to capture sensor-challenging context, such personal opinions.
To define this free text ESM:
try { ESMFactory factory = new ESMFactory(); //define ESM question ESM_Freetext esmFreetext = new ESM_Freetext(); esmFreetext.setTitle("Freetext") .setTrigger("an esm queue from AWARE") .setSubmitButton("OK") .setInstructions("Open-ended text input"); //add them to the factory factory.addESM(esmFreetext); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":1,"esm_title":"Freetext","esm_trigger":"an esm queue from AWARE","esm_submit":"OK","esm_instructions":"Open-ended text input"}}
Radio
This ESM only allows the user to select a single option from a list of alternatives.
One of the options can be defined as “Other”, which will prompt the user to be more specific, replacing “Other” with the users’ defined option.
In case you wish to use a different word than “Other” you may override it on /res/values/strings.xml file on your project. To do so, add a new string resource with your new word and what appears on the title requesting the user to be more specific:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="aware_esm_other">Other</string> <string name="aware_esm_other_follow">Can you be more specific, please?</string> </resources>
To define this radio ESM:
try { ESMFactory factory = new ESMFactory(); //define ESM question ESM_Radio esmRadio = new ESM_Radio(); esmRadio.addRadio("Radio 1") .addRadio("Radio 2") .setTitle("Radios") .setInstructions("Radios ESM") .setSubmitButton("OK"); //add them to the factory factory.addESM(esmRadio); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":2,"esm_radios":["Radio 1","Radio 2"],"esm_title":"Radios","esm_instructions":"Radios ESM","esm_submit":"OK"}}
Checkbox
This ESM allows the user to select one or more options from a list of alternatives. Similar to the Radio ESM, one of the options can be defined as “Other”, which will prompt the user to be more specific, replacing “Other” with the users’ defined option.
In case you wish to use a different word than “Other” you may override it on /res/values/strings.xml file on your project. To do so, add a new string resource with your new word and what appears on the title requesting the user to be more specific:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="aware_esm_other">Other</string> <string name="aware_esm_other_follow">Can you be more specific, please?</string> </resources>
To define this checkbox ESM:
try { ESMFactory factory = new ESMFactory(); //define ESM question ESM_Checkbox esmCheckbox = new ESM_Checkbox(); esmCheckbox.addCheck("Option 1") .addCheck("Option 2") .addCheck("Other") .setTitle("Checkbox") .setSubmitButton("OK") .setInstructions("Multiple choice is allowed"); //add them to the factory factory.addESM(esmCheckbox); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":3,"esm_checkboxes":["Option 1","Option 2","Other"],"esm_title":"Checkbox","esm_submit":"OK","esm_instructions":"Multiple choice is allowed"}}
Likert Scale
This ESM allows the user to provide ratings, between 0 and 5/7, at 0.5/1 increments. The likert scale labels are also customisable. The default rating is no rating.
To define this likert scale ESM:
try { ESMFactory factory = new ESMFactory(); //define ESM question ESM_Likert esmLikert = new ESM_Likert(); esmLikert.setLikertMax(5) .setLikertMaxLabel("Great") .setLikertMinLabel("Poor") .setLikertStep(1) .setTitle("Likert") .setInstructions("Likert ESM") .setSubmitButton("OK"); //add them to the factory factory.addESM(esmLikert); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":4,"esm_likert_max":5,"esm_likert_max_label":"Great","esm_likert_min_label":"Poor","esm_likert_step":1,"esm_title":"Likert","esm_instructions":"Likert ESM","esm_submit":"OK"}}
Quick Answer
This ESM allows the user to quickly answer the ESM. The button arrangement is fluid, to support more or less inputs. Keep in mind that a dialog can only grow as much as there is screen estate and we don’t recommend more than 6 items here.
Unlike previous ESMs , there is no “Cancel” button for this type of ESM. However, the user can dismiss the questionnaire by pressing the Home or Back button on the device.
To define this quick answer ESM:
try { ESMFactory factory = new ESMFactory(); //define ESM question ESM_QuickAnswer esmQuickAnswer = new ESM_QuickAnswer(); esmQuickAnswer.addQuickAnswer("Yes") .addQuickAnswer("No") .setInstructions("Quick Answers ESM"); //add them to the factory factory.addESM(esmQuickAnswer); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":5,"esm_quick_answers":["Yes","No"],"esm_instructions":"Quick Answers ESM"}}
Scale
This ESM allows the user to select a value within a range of values. The range can be positive (e.g., X to Y) where X and Y are both positive numbers; or negatively balanced (e.g., -X to X), where X is the same value.
To define this scale ESM:
try { ESMFactory factory = new ESMFactory(); //define ESM question ESM_Scale esmScale = new ESM_Scale(); esmScale.setScaleMax(100) .setScaleMin(0) .setScaleStart(50) .setScaleMaxLabel("Perfect") .setScaleMinLabel("Poor") .setScaleStep(10) .setTitle("Scale") .setInstructions("Scale ESM") .setExpirationThreshold(0) .setSubmitButton("OK"); //add them to the factory factory.addESM(esmScale); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":6,"esm_scale_max":100,"esm_scale_min":0,"esm_scale_start":50,"esm_scale_max_label":"Perfect","esm_scale_min_label":"Poor","esm_scale_step":10,"esm_title":"Scale","esm_instructions":"Scale ESM","esm_submit":"OK"}}
Numeric
This ESM allows the user to provide numeric input. Numbers inputted by the participant can be both full numbers and decimals.
To define this numeric ESM:
try { ESMFactory factory = new ESMFactory(); //define ESM question ESM_Number esmNumeric = new ESM_Number(); esmNumeric.setTitle("Numeric") .setInstructions("The user can enter a number") .setSubmitButton("OK"); //add them to the factory factory.addESM(esmNumeric); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":9,"esm_title":"Numeric","esm_instructions":"The user can enter a number","esm_submit":"OK"}}
Web
This ESM allows you to ask the user to answer an online survey.
To define this ESM:
try { ESMFactory factory = new ESMFactory(); ESM_Web web = new ESM_Web(); web.setURL("https://www.google.com"); web.setTitle("Web survey"); web.setInstructions("Fill out this survey. Press OK when finished"); web.setSubmitButton("OK"); factory.addESM(web); ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question as JSON:
{"esm":{"esm_type":10,"esm_title":"ESM Web","esm_instructions":"Please fill out this online survey. Press OK when done.","esm_submit":"OK","esm_url":"https://www.google.com"}}
Step-by-step ESM Questionnaire
To create a step-by-step questionnaire, we can group multiple ESM definitions together. For example, if we want to combine a free text, with a radio question, we can define them as a queue, where they are shown sequentially:
try { ESMFactory factory = new ESMFactory(); ESM_Freetext esmFreetext = new ESM_Freetext(); esmFreetext.setTitle("What is on your mind?") .setSubmitButton("Next") .setInstructions("Tell us how you feel"); ESM_Radio esmRadio = new ESM_Radio(); esmRadio.addRadio("Bored") .addRadio("Fantastic") .setTitle("Are you...") .setInstructions("Pick one!") .setSubmitButton("OK"); //add them to the factory factory.addESM(esmFreetext); factory.addESM(esmRadio); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this queue of questions as a JSON array:
[{"esm":{"esm_type":1,"esm_title":"What is on your mind?","esm_instructions":"Tell us how you feel","esm_submit":"Next"}},{"esm":{"esm_type":2,"esm_radios":["Bored","Fantastic"],"esm_title":"Are you...","esm_instructions":"Pick one!","esm_submit":"OK"}}]
ESM Questionnaire Flow
To make the next question displayed to the participant dependent on the participant’s answer to the current question, make use of the flow logic for ESMs. For example, to present a different question following a boolean yes/no choice, define your ESM queue like this:
try { ESMFactory factory = new ESMFactory(); ESM_PAM q1 = new ESM_PAM(); q1.setTitle("Your mood") .setInstructions("Choose the closest to how you feel right now.") .setSubmitButton("Thanks!"); ESM_Radio q2 = new ESM_Radio(); q2.addRadio("Eating") .addRadio("Working") .addRadio("Not alone") .setTitle("Why is that?") .setSubmitButton("Thanks!"); ESM_QuickAnswer q0 = new ESM_QuickAnswer(); q0.addQuickAnswer("Yes") .addQuickAnswer("No") .setTitle("Is this a good time to answer?") .addFlow("Yes", q1.build()) .addFlow("No", q2.build()); //add ONLY the last one (q0) to the factory, as the others (q1, q2) are part of the flow for Yes and No factory.addESM(q0); //Queue them ESM.queueESM(context, factory.build()); } catch (JSONException e) { e.printStackTrace(); }
Internally, AWARE defines this question flow as JSON:
{"esm":{"esm_type":5,"esm_quick_answers":["Yes","No"],"esm_title":"Is this a good time to answer?","esm_flows":[{"user_answer":"Yes","next_esm":{"esm":{"esm_type":8,"esm_title":"Your mood","esm_instructions":"Choose the closest to how you feel right now.","esm_submit":"Thanks!"}}},{"user_answer":"No","next_esm":{"esm":{"esm_type":2,"esm_radios":["Eating","Working","Not alone"],"esm_title":"Why is that?","esm_submit":"Thanks!"}}}]}}
iOS
Please check a tutoral for developing ESMs on iOS or iOS ESM plugin.