@@ -0,0 +1,24 @@
+[droid-vibrator]
+
+# For each string in EFFECT_LIST define a sequence
+EFFECT_LIST = touch,short,strong,alarm,notice,message,ringtone
+
+# Sequence has following syntax:
+# sequence_name = <action>=<value>
+#
+# Actions:
+# on - Run vibra for value ms
+# pause - Pause for value ms
+# repeat - Repeat for value times, or forever for forever
+#
+# For example, to have vibra run twice for 200ms and pause for 100ms:
+# vibra = on=200,pause=100,repeat=1
+#
+
+touch = on=20
+short = on=33
+strong = on=66
+alarm = on=1000,pause=500,repeat=forever
+notice = on=500,pause=300,repeat=1
+message = on=200,pause=200,repeat=1
+ringtone = on=2000,pause=500,repeat=forever
|
@@ -3,7 +3,7 @@
*
* Copyright (C) 2010 Nokia Corporation.
* Contact: Xun Chen <xun.chen@nokia.com>
- * Copyright (C) 2014 Jolla Ltd.
+ * Copyright (C) 2014,2017 Jolla Ltd.
* Contact: Simonas Leleiva <simonas.leleiva@jollamobile.com>
*
* This work is free software; you can redistribute it and/or
@@ -23,27 +23,230 @@
#include <ngf/plugin.h>
#include <ngf/haptic.h>
+#include <android-version.h>
+#if ANDROID_VERSION_MAJOR >= 7
+#include <hardware/vibrator.h>
+#else
#include <hardware_legacy/vibrator.h>
+#endif
#define AV_KEY "plugin.droid-vibrator.data"
#define LOG_CAT "droid-vibrator: "
-typedef struct _DroidVibratorData
+#define EFFECT_LIST "EFFECT_LIST"
+#define EFFECT_LIST_DELIMITER ","
+#define EFFECT_VIBRA "on"
+#define EFFECT_PAUSE "pause"
+#define EFFECT_REPEAT "repeat"
+#define EFFECT_REPEAT_FOREVER "forever"
+#define MAX_STEP_TIME (10000) /* 10 seconds */
+#define MIN_STEP_TIME (1) /* 1 ms */
+#define MAX_REPEATS (100)
+#define REPEAT_FOREVER (-1)
+
+enum EffectStepType {
+ EFFECT_STEP_NONE,
+ /* vibrator on for *value* ms */
+ EFFECT_STEP_VIBRA,
+ /* pause for *value* ms */
+ EFFECT_STEP_PAUSE,
+ /* repeat the whole sequence from the beginning of
+ the sqeuence *value* times,
+ -1 forever, 0 no repeat, 1+ n repeats */
+ EFFECT_STEP_REPEAT
+};
+
+typedef struct DroidVibratorEffectStep
+{
+ enum EffectStepType type;
+ int value;
+} DroidVibratorEffectStep;
+
+typedef struct DroidVibratorEffect
+{
+ GSList *steps;
+ int repeat;
+} DroidVibratorEffect;
+
+typedef struct DroidVibratorData
{
- NRequest *request;
- NSinkInterface *iface;
- guint timeout_id;
+ NRequest *request;
+ NSinkInterface *iface;
+ guint sequence_id;
+ DroidVibratorEffect *current_effect;
+ GSList *current_step;
+ int repeat_count;
} DroidVibratorData;
+static GHashTable *plugin_effects;
+static const NProplist *plugin_properties;
+
+#if ANDROID_VERSION_MAJOR >= 7
+static vibrator_device_t *dev;
+#endif
+
N_PLUGIN_NAME ("droid-vibrator")
-N_PLUGIN_VERSION ("0.1")
+N_PLUGIN_VERSION ("0.2")
N_PLUGIN_DESCRIPTION ("Haptic feedback using Droid Vibrator HAL via libhybris")
+static void
+effect_free (gpointer data)
+{
+ DroidVibratorEffect *effect = (DroidVibratorEffect*) data;
+ g_slist_free_full (effect->steps, g_free);
+ g_free (effect);
+}
+
+static DroidVibratorEffect*
+effect_parse (const char *name, const NProplist *properties)
+{
+ DroidVibratorEffect *effect = NULL;
+ const gchar *sequence = NULL;
+ gchar **sequence_parts = NULL;
+ int i;
+
+ if (!(sequence = n_proplist_get_string (properties, name))) {
+ N_WARNING (LOG_CAT "sequence missing for %s", name);
+ return NULL;
+ }
+ sequence_parts = g_strsplit(sequence, EFFECT_LIST_DELIMITER, 0);
+
+ effect = g_new0 (DroidVibratorEffect, 1);
+
+ for (i = 0; sequence_parts[i]; i++) {
+ enum EffectStepType step_type = EFFECT_STEP_NONE;
+ DroidVibratorEffectStep *s;
+ gchar **step;
+ gint64 value;
+
+ step = g_strsplit(sequence_parts[i], "=", 0);
+ if (!step[0] || !step[1]) {
+ g_strfreev (step);
+ effect_free (effect);
+ effect = NULL;
+ N_WARNING (LOG_CAT "bad sequence string '%s', ignoring sequence %s", sequence, name);
+ goto done;
+ }
+
+ if (g_strcmp0 (step[0], EFFECT_VIBRA) == 0)
+ step_type = EFFECT_STEP_VIBRA;
+ else if (g_strcmp0 (step[0], EFFECT_PAUSE) == 0)
+ step_type = EFFECT_STEP_PAUSE;
+ else if (g_strcmp0 (step[0], EFFECT_REPEAT) == 0)
+ step_type = EFFECT_STEP_REPEAT;
+
+ switch (step_type) {
+ case EFFECT_STEP_VIBRA:
+ /* fall through */
+ case EFFECT_STEP_PAUSE:
+ s = g_new0 (DroidVibratorEffectStep, 1);
+ s->type = step_type;
+ value = g_ascii_strtoll (step[1], NULL, 10);
+ if (value > MAX_STEP_TIME)
+ value = MAX_STEP_TIME;
+ else if (value < MIN_STEP_TIME)
+ value = MIN_STEP_TIME;
+ s->value = (int) value;
+ effect->steps = g_slist_append (effect->steps, s);
+ break;
+
+ case EFFECT_STEP_REPEAT:
+ if (g_strcmp0 (step[1], EFFECT_REPEAT_FOREVER) == 0)
+ value = REPEAT_FOREVER;
+ else {
+ value = g_ascii_strtoll (step[1], NULL, 10);
+ if (value > MAX_REPEATS)
+ value = MAX_REPEATS;
+ else if (value <= -1)
+ value = REPEAT_FOREVER;
+ }
+ effect->repeat = (int) value;
+ break;
+
+ default:
+ N_WARNING (LOG_CAT "incorrect sequence type %s, ignoring", step[0]);
+ break;
+ }
+
+ g_strfreev (step);
+ }
+
+done:
+ g_strfreev (sequence_parts);
+
+ if (effect && !effect->steps) {
+ N_WARNING (LOG_CAT "no valid effect steps, ignoring sequence %s", name);
+ effect_free (effect);
+ effect = NULL;
+ }
+
+ return effect;
+}
+
+static GHashTable*
+effects_parse (const NProplist *properties)
+{
+ GHashTable *effects = NULL;
+ const gchar *effect_data;
+ gchar **effect_names;
+ int i;
+
+ effect_data = n_proplist_get_string (properties, EFFECT_LIST);
+
+ if (!effect_data) {
+ N_WARNING (LOG_CAT "no " EFFECT_LIST " defined");
+ return NULL;
+ }
+
+ effect_names = g_strsplit (effect_data, EFFECT_LIST_DELIMITER, 0);
+ if (!effect_names[0]) {
+ N_WARNING (LOG_CAT "Empty " EFFECT_LIST "string");
+ goto effect_list_done;
+ }
+
+ N_DEBUG (LOG_CAT "creating effect list for %s", effect_data);
+
+ effects = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ effect_free);
+
+ for (i = 0; effect_names[i]; i++) {
+ DroidVibratorEffect *e;
+ if ((e = effect_parse (effect_names[i], properties)))
+ g_hash_table_insert (effects, g_strdup(effect_names[i]), e);
+ }
+
+effect_list_done:
+ g_strfreev(effect_names);
+
+ return effects;
+}
+
static int
droid_vibrator_sink_initialize (NSinkInterface *iface)
{
(void) iface;
N_DEBUG (LOG_CAT "sink initialize");
+
+#if ANDROID_VERSION_MAJOR >= 7
+ struct hw_module_t *hwmod;
+
+ hw_get_module (VIBRATOR_HARDWARE_MODULE_ID, (const hw_module_t **)(&hwmod));
+ g_assert(hwmod != NULL);
+
+ if (vibrator_open (hwmod, &dev) < 0) {
+ N_DEBUG (LOG_CAT "unable to open vibrator device");
+ return FALSE;
+ }
+#endif
+
+ g_assert(plugin_properties);
+ plugin_effects = effects_parse (plugin_properties);
+
+ if (!plugin_effects)
+ return FALSE;
+
return TRUE;
}
@@ -52,6 +255,11 @@
{
(void) iface;
N_DEBUG (LOG_CAT "sink shutdown");
+
+ if (plugin_effects) {
+ g_hash_table_destroy (plugin_effects);
+ plugin_effects = NULL;
+ }
}
static int
@@ -64,13 +272,33 @@
static int
droid_vibrator_sink_prepare (NSinkInterface *iface, NRequest *request)
{
+ DroidVibratorData *data;
+ DroidVibratorEffect *effect;
+ const NProplist *props;
+ const gchar *key;
+
N_DEBUG (LOG_CAT "sink prepare");
- DroidVibratorData *data = g_slice_new0 (DroidVibratorData);
+ props = n_request_get_properties (request);
+
+ key = n_proplist_get_string(props, HAPTIC_TYPE_KEY);
+ if (key == NULL) {
+ N_DEBUG (LOG_CAT "no effect key found for this effect");
+ return FALSE;
+ }
+
+ if (!(effect = g_hash_table_lookup (plugin_effects, key))) {
+ N_DEBUG (LOG_CAT "no effect with key %s found for this effect", key);
+ return FALSE;
+ }
- data->request = request;
- data->iface = iface;
- data->timeout_id = 0;
+ data = g_slice_new0 (DroidVibratorData);
+ data->request = request;
+ data->iface = iface;
+ data->sequence_id = 0;
+ data->current_effect = effect;
+ data->current_step = effect->steps;
+ data->repeat_count = effect->repeat;
n_request_store_data (request, AV_KEY, data);
n_sink_interface_synchronize (iface, request);
@@ -78,50 +306,91 @@
return TRUE;
}
+static void
+sequence_play (DroidVibratorData *data);
+
static gboolean
-timeout_cb (gpointer userdata)
+sequence_cb (gpointer userdata)
{
- N_DEBUG (LOG_CAT "sink play timeout");
-
DroidVibratorData *data = (DroidVibratorData*) userdata;
- g_assert (data != NULL);
+ g_assert (data);
- data->timeout_id = 0;
- n_sink_interface_complete (data->iface, data->request);
+ data->sequence_id = 0;
+ data->current_step = g_slist_next (data->current_step);
+ sequence_play (data);
return FALSE;
}
+static void
+sequence_play (DroidVibratorData *data)
+{
+ DroidVibratorEffectStep *step;
+
+ if (!data->current_step) {
+ if (data->repeat_count == REPEAT_FOREVER)
+ data->current_step = data->current_effect->steps;
+ else if (data->repeat_count > 0) {
+ data->repeat_count--;
+ data->current_step = data->current_effect->steps;
+ } else {
+ n_sink_interface_complete (data->iface, data->request);
+ return;
+ }
+ }
+
+ step = g_slist_nth_data (data->current_step, 0);
+
+ data->sequence_id = g_timeout_add (step->value, sequence_cb, data);
+ if (step->type == EFFECT_STEP_VIBRA)
+#if ANDROID_VERSION_MAJOR >= 7
+ dev->vibrator_on (dev, step->value);
+#else
+ vibrator_on (step->value);
+#endif
+
+}
+
static int
droid_vibrator_sink_play (NSinkInterface *iface, NRequest *request)
{
+ DroidVibratorData *data;
+ (void) iface;
+
N_DEBUG (LOG_CAT "sink play");
- (void) iface;
- // TODO: refer to ffmemless.ini and build own config with durations for
- // each event
- int duration_ms = 33;
-
- DroidVibratorData *data = (DroidVibratorData*) n_request_get_data (request, AV_KEY);
- g_assert (data != NULL);
-
- vibrator_on(duration_ms);
-
- // underlying Droid API cannot tell when playback finishes, so we always
- // assume it worked and finished (no big knock-on effects if we don't time
- // it well: tests show subsequent rapid haptics feedback worked fine)
- data->timeout_id = g_timeout_add (duration_ms, timeout_cb, data);
+ data = (DroidVibratorData*) n_request_get_data (request, AV_KEY);
+ g_assert (data);
+ sequence_play (data);
return TRUE;
}
+static void
+sequence_stop (DroidVibratorData *data)
+{
+ if (data->sequence_id > 0) {
+ g_source_remove (data->sequence_id);
+ data->sequence_id = 0;
+#if ANDROID_VERSION_MAJOR >= 7
+ dev->vibrator_off (dev);
+#else
+ vibrator_off ();
+#endif
+ }
+}
+
static int
droid_vibrator_sink_pause (NSinkInterface *iface, NRequest *request)
{
+ DroidVibratorData *data;
+ (void) iface;
+
N_DEBUG (LOG_CAT "sink pause");
- (void) iface;
- (void) request;
+ data = (DroidVibratorData*) n_request_get_data (request, AV_KEY);
+ g_assert (data);
+ sequence_stop (data);
return TRUE;
}
@@ -129,18 +398,14 @@
static void
droid_vibrator_sink_stop (NSinkInterface *iface, NRequest *request)
{
- N_DEBUG (LOG_CAT "sink stop");
-
+ DroidVibratorData *data;
(void) iface;
- DroidVibratorData *data = (DroidVibratorData*) n_request_get_data (request, AV_KEY);
- g_assert (data != NULL);
-
- if (data->timeout_id > 0) {
- g_source_remove (data->timeout_id);
- data->timeout_id = 0;
- }
+ N_DEBUG (LOG_CAT "sink stop");
+ data = (DroidVibratorData*) n_request_get_data (request, AV_KEY);
+ g_assert (data);
+ sequence_stop (data);
g_slice_free (DroidVibratorData, data);
}
@@ -159,6 +424,10 @@
.stop = droid_vibrator_sink_stop
};
+#if ANDROID_VERSION_MAJOR >= 7
+ dev = NULL;
+#endif
+ plugin_properties = n_plugin_get_params (plugin);
n_plugin_register_sink (plugin, &decl);
return TRUE;
|