[RFC] HFP support into oFono and BlueZ
by Gustavo F. Padovan
Hi,
These patches implement the new API for the Audio Gateway in BlueZ. It
follows the last version of the HandsfreeGateway and HandsfreeAgent
Intefaces API.
The first two patches is for BlueZ and the other for oFono. You can
test it with using enable-modem and test-voicecall scripts into the
test dir of oFono.
Feel free to test it and send me your comments. We have some bugs yet.
The audio part is not working yet. We are going to work on pulseaudio
this week to get this done soon.
Regards,
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
8 years, 6 months
[PATCH 1/3] hfp: create modem for new devices paired on runtime
by Gustavo F. Padovan
It listens the Paired property to create a modem to the recently paired
devices. It also renames added_watch to adapter_watch, a more proper
name.
---
plugins/hfp.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 57 insertions(+), 5 deletions(-)
diff --git a/plugins/hfp.c b/plugins/hfp.c
index 981b05b..3e41342 100644
--- a/plugins/hfp.c
+++ b/plugins/hfp.c
@@ -66,6 +66,7 @@ static const char *cmer_prefix[] = { "+CMER:", NULL };
static const char *chld_prefix[] = { "+CHLD:", NULL };
static DBusConnection *connection;
+static GHashTable *uuid_hash = NULL;
static void hfp_debug(const char *str, void *user_data)
{
@@ -427,6 +428,7 @@ static int hfp_create_modem(const char *device)
{
struct ofono_modem *modem;
struct hfp_data *data;
+ const char *path;
ofono_info("Using device: %s", device);
@@ -451,6 +453,9 @@ static int hfp_create_modem(const char *device)
ofono_modem_set_data(modem, data);
ofono_modem_register(modem);
+ path = ofono_modem_get_path(modem);
+ g_hash_table_insert(uuid_hash, g_strdup(device), g_strdup(path));
+
return 0;
free:
@@ -465,6 +470,9 @@ static void parse_uuids(DBusMessageIter *i, const char *device)
DBusMessageIter variant, ai;
const char *value;
+ if (g_hash_table_lookup(uuid_hash, device))
+ return;
+
dbus_message_iter_recurse(i, &variant);
dbus_message_iter_recurse(&variant, &ai);
@@ -624,6 +632,33 @@ static gboolean adapter_added(DBusConnection *connection, DBusMessage *message,
return TRUE;
}
+static gboolean uuid_emitted(DBusConnection *connection, DBusMessage *message,
+ void *user_data)
+{
+ const char *device, *property;
+ DBusMessageIter iter;
+
+ dbus_message_iter_init(message, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&iter, &property);
+ if (g_str_equal(property, "UUIDs") == FALSE)
+ return TRUE;
+
+ if (!dbus_message_iter_next(&iter))
+ return FALSE;
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+ return FALSE;
+
+ device = dbus_message_get_path(message);
+ parse_uuids(&iter, device);
+
+ return TRUE;
+}
+
static void list_adapters_cb(DBusPendingCall *call, gpointer user_data)
{
DBusError err;
@@ -725,6 +760,8 @@ static void hfp_remove(struct ofono_modem *modem)
hfp_unregister_ofono_handsfree(modem);
+ g_hash_table_remove(uuid_hash, data->handsfree_path);
+
g_free(data->handsfree_path);
g_free(data);
@@ -798,7 +835,8 @@ static struct ofono_modem_driver hfp_driver = {
.post_sim = hfp_post_sim,
};
-static guint added_watch;
+static guint adapter_watch;
+static guint uuid_watch;
static int hfp_init(void)
{
@@ -809,12 +847,21 @@ static int hfp_init(void)
connection = ofono_dbus_get_connection();
- added_watch = g_dbus_add_signal_watch(connection, NULL, NULL,
+ adapter_watch = g_dbus_add_signal_watch(connection, NULL, NULL,
BLUEZ_MANAGER_INTERFACE,
"AdapterAdded",
adapter_added, NULL, NULL);
- if (added_watch == 0) {
+ uuid_watch = g_dbus_add_signal_watch(connection, NULL, NULL,
+ BLUEZ_DEVICE_INTERFACE,
+ "PropertyChanged",
+ uuid_emitted, NULL, NULL);
+
+
+ uuid_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+
+ if (adapter_watch == 0 || uuid_watch == 0) {
err = -EIO;
goto remove;
}
@@ -828,7 +875,9 @@ static int hfp_init(void)
return 0;
remove:
- g_dbus_remove_watch(connection, added_watch);
+ g_dbus_remove_watch(connection, adapter_watch);
+ g_dbus_remove_watch(connection, uuid_watch);
+ g_hash_table_destroy(uuid_hash);
dbus_connection_unref(connection);
@@ -837,9 +886,12 @@ remove:
static void hfp_exit(void)
{
- g_dbus_remove_watch(connection, added_watch);
+ g_dbus_remove_watch(connection, adapter_watch);
+ g_dbus_remove_watch(connection, uuid_watch);
ofono_modem_driver_unregister(&hfp_driver);
+
+ g_hash_table_destroy(uuid_hash);
}
OFONO_PLUGIN_DEFINE(hfp, "Hands-Free Profile Plugins", VERSION,
--
1.6.4.4
10 years, 7 months
Testing asset for oFono
by Aki Niemi
Hi,
There are currently a bunch of helper scripts under /test for the D-Bus
API in addition to unit tests, and I'm wondering if there are any plans
to automate especially the former into a suite that could be run
semi-regularly, or even at 'make distcheck'?
I think it would be useful to have something like the current scripts,
but modified to be actual test cases as well as more easily
parametrized.
The tests could then be driven with a set of parameters (which cases to
run, what numbers to call, etc.) and a default test profile to work
with, for example, the default phonesim configuration.
Other profiles could then be made for other test environments, such as a
live modem and a test network or network simulator.
Any thoughts on this?
Cheers,
Aki
10 years, 9 months
Access to SIM card when Modem is not "Powered"
by Pekka Pessi
Hi all,
I think the Modem "Powered" property is meant to control the radios
(something like at+cfun=0 vs. at+cfun=1..). Now core automatically
removes all the atoms in case modem has "Powered" false. However, the
SIM card should be accessible while the radios are off (cfun=0) so
that PIN code could be entered. If the +CFUN=1 is given before PIN
code is entered, the modem registers to network in limited service
(emergency call only) mode. Perhaps it would be better to let the
modem driver itself decide which atoms are active when it is not fully
powered?
--
Pekka.Pessi mail at nokia.com
10 years, 10 months
[PATCH 3/5][RfC] Remove post-sim atoms after SIM is extracted.
by Andrzej Zaborowski
---
src/modem.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 48 insertions(+), 0 deletions(-)
diff --git a/src/modem.c b/src/modem.c
index b935328..6dfa73a 100644
--- a/src/modem.c
+++ b/src/modem.c
@@ -52,6 +52,7 @@ enum ofono_property_type {
struct ofono_modem {
char *path;
GSList *atoms;
+ GSList *pre_sim_atoms;
struct ofono_watchlist *atom_watches;
GSList *interface_list;
unsigned int call_ids;
@@ -64,6 +65,7 @@ struct ofono_modem {
struct ofono_sim *sim;
unsigned int sim_watch;
unsigned int sim_ready_watch;
+ unsigned int sim_removed_watch;
const struct ofono_modem_driver *driver;
void *driver_data;
char *driver_type;
@@ -300,6 +302,7 @@ void __ofono_atom_free(struct ofono_atom *atom)
struct ofono_modem *modem = atom->modem;
modem->atoms = g_slist_remove(modem->atoms, atom);
+ modem->pre_sim_atoms = g_slist_remove(modem->pre_sim_atoms, atom);
__ofono_atom_unregister(atom);
@@ -329,7 +332,35 @@ static void remove_all_atoms(struct ofono_modem *modem)
}
g_slist_free(modem->atoms);
+ g_slist_free(modem->pre_sim_atoms);
modem->atoms = NULL;
+ modem->pre_sim_atoms = NULL;
+}
+
+static void remove_post_sim_atoms(struct ofono_modem *modem)
+{
+ GSList *l;
+ struct ofono_atom *atom;
+
+ if (modem == NULL)
+ return;
+
+ for (l = modem->atoms; l; l = l->next) {
+ atom = l->data;
+
+ if (g_slist_find(modem->pre_sim_atoms, atom))
+ continue;
+
+ __ofono_atom_unregister(atom);
+
+ if (atom->destruct)
+ atom->destruct(atom);
+
+ g_free(atom);
+ }
+
+ g_slist_free(modem->atoms);
+ modem->atoms = g_slist_copy(modem->pre_sim_atoms);
}
static DBusMessage *modem_get_properties(DBusConnection *conn,
@@ -1121,12 +1152,25 @@ static void modem_sim_ready(void *user)
{
struct ofono_modem *modem = user;
+ if (modem->pre_sim_atoms == NULL)
+ modem->pre_sim_atoms = g_slist_copy(modem->atoms);
+
if (modem->driver->post_sim)
modem->driver->post_sim(modem);
__ofono_history_probe_drivers(modem);
}
+static void modem_sim_removed(void *user)
+{
+ struct ofono_modem *modem = user;
+
+ if (modem->pre_sim_atoms == NULL)
+ return;
+
+ remove_post_sim_atoms(modem);
+}
+
static void sim_watch(struct ofono_atom *atom,
enum ofono_atom_watch_condition cond, void *data)
{
@@ -1134,6 +1178,7 @@ static void sim_watch(struct ofono_atom *atom,
if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) {
modem->sim_ready_watch = 0;
+ modem->sim_removed_watch = 0;
return;
}
@@ -1141,6 +1186,9 @@ static void sim_watch(struct ofono_atom *atom,
modem->sim_ready_watch = ofono_sim_add_ready_watch(modem->sim,
modem_sim_ready,
modem, NULL);
+ modem->sim_removed_watch = ofono_sim_add_removed_watch(modem->sim,
+ modem_sim_removed,
+ modem, NULL);
if (ofono_sim_get_ready(modem->sim))
modem_sim_ready(modem);
--
1.6.1
10 years, 10 months
[PATCH 2/5][RfC] Release calls when SIM is removed.
by Andrzej Zaborowski
---
src/voicecall.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 56 insertions(+), 7 deletions(-)
diff --git a/src/voicecall.c b/src/voicecall.c
index 8bf6379..b0aa5cd 100644
--- a/src/voicecall.c
+++ b/src/voicecall.c
@@ -54,6 +54,8 @@ struct ofono_voicecall {
gint emit_calls_source;
gint emit_multi_source;
unsigned int sim_watch;
+ unsigned int sim_ready_watch;
+ unsigned int sim_removed_watch;
const struct ofono_voicecall_driver *driver;
void *driver_data;
struct ofono_atom *atom;
@@ -1784,6 +1786,10 @@ static void voicecall_unregister(struct ofono_atom *atom)
static void voicecall_remove(struct ofono_atom *atom)
{
struct ofono_voicecall *vc = __ofono_atom_get_data(atom);
+ struct ofono_modem *modem = __ofono_atom_get_modem(vc->atom);
+ struct ofono_atom *sim_atom =
+ __ofono_modem_find_atom(modem, OFONO_ATOM_TYPE_SIM);
+ struct ofono_sim *sim = NULL;
DBG("atom: %p", atom);
@@ -1805,6 +1811,19 @@ static void voicecall_remove(struct ofono_atom *atom)
vc->new_en_list = NULL;
}
+ if (sim_atom && __ofono_atom_get_registered(sim_atom))
+ sim = __ofono_atom_get_data(sim_atom);
+
+ if (sim && vc->sim_ready_watch) {
+ ofono_sim_remove_ready_watch(sim, vc->sim_ready_watch);
+ vc->sim_ready_watch = 0;
+ }
+
+ if (sim && vc->sim_removed_watch) {
+ ofono_sim_remove_removed_watch(sim, vc->sim_removed_watch);
+ vc->sim_removed_watch = 0;
+ }
+
g_free(vc);
}
@@ -1843,6 +1862,34 @@ struct ofono_voicecall *ofono_voicecall_create(struct ofono_modem *modem,
return vc;
}
+static void sim_ready_watch(void *user)
+{
+ struct ofono_voicecall *vc = user;
+ struct ofono_modem *modem = __ofono_atom_get_modem(vc->atom);
+ struct ofono_atom *sim_atom =
+ __ofono_modem_find_atom(modem, OFONO_ATOM_TYPE_SIM);
+ struct ofono_sim *sim = __ofono_atom_get_data(sim_atom);
+
+ /* Try both formats, only one or none will work */
+ ofono_sim_read(sim, SIM_EFECC_FILEID,
+ OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
+ ecc_g2_read_cb, vc);
+ ofono_sim_read(sim, SIM_EFECC_FILEID,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ ecc_g3_read_cb, vc);
+}
+
+static void sim_removed_watch(void *user)
+{
+ struct ofono_voicecall *vc = user;
+
+ vc->flags |= VOICECALLS_FLAG_MULTI_RELEASE;
+
+ /* TODO: Don't hang up emergency calls */
+ voicecalls_release_queue(vc, vc->call_list);
+ voicecalls_release_next(vc);
+}
+
static void sim_watch(struct ofono_atom *atom,
enum ofono_atom_watch_condition cond, void *data)
{
@@ -1850,16 +1897,18 @@ static void sim_watch(struct ofono_atom *atom,
struct ofono_sim *sim = __ofono_atom_get_data(atom);
if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) {
+ vc->sim_ready_watch = 0;
+ vc->sim_removed_watch = 0;
return;
}
- /* Try both formats, only one or none will work */
- ofono_sim_read(sim, SIM_EFECC_FILEID,
- OFONO_SIM_FILE_STRUCTURE_TRANSPARENT,
- ecc_g2_read_cb, vc);
- ofono_sim_read(sim, SIM_EFECC_FILEID,
- OFONO_SIM_FILE_STRUCTURE_FIXED,
- ecc_g3_read_cb, vc);
+ vc->sim_ready_watch = ofono_sim_add_ready_watch(sim,
+ sim_ready_watch, vc, NULL);
+ vc->sim_removed_watch = ofono_sim_add_removed_watch(sim,
+ sim_removed_watch, vc, NULL);
+
+ if (ofono_sim_get_ready(sim))
+ sim_ready_watch(vc);
}
void ofono_voicecall_register(struct ofono_voicecall *vc)
--
1.6.1
10 years, 10 months
[PATCH 1/5][RfC] Add functions to notify core of SIM insertion/removal/proactive command.
by Andrzej Zaborowski
Make every plugin generate a sim inserted event on start. For devices
with removable SIM card, the event should be emitted after the
plugin detects it.
---
include/sim.h | 12 +++++-
plugins/atgen.c | 5 ++-
plugins/calypso.c | 5 ++-
plugins/em770.c | 5 ++-
plugins/g1.c | 5 ++-
plugins/hso.c | 5 ++-
plugins/huawei.c | 5 ++-
plugins/mbm.c | 5 ++-
plugins/palmpre.c | 5 ++-
plugins/phonesim.c | 5 ++-
plugins/ste.c | 5 ++-
src/sim.c | 113 +++++++++++++++++++++++++++++++++++++++++-----------
12 files changed, 141 insertions(+), 34 deletions(-)
diff --git a/include/sim.h b/include/sim.h
index 6ff29f7..2cf6afb 100644
--- a/include/sim.h
+++ b/include/sim.h
@@ -179,8 +179,18 @@ unsigned int ofono_sim_add_ready_watch(struct ofono_sim *sim,
void ofono_sim_remove_ready_watch(struct ofono_sim *sim, unsigned int id);
+unsigned int ofono_sim_add_removed_watch(struct ofono_sim *sim,
+ ofono_sim_ready_notify_cb_t cb,
+ void *data, ofono_destroy_func destroy);
+
+void ofono_sim_remove_removed_watch(struct ofono_sim *sim, unsigned int id);
+
int ofono_sim_get_ready(struct ofono_sim *sim);
-void ofono_sim_set_ready(struct ofono_sim *sim);
+
+void ofono_sim_inserted(struct ofono_sim *sim);
+void ofono_sim_removed(struct ofono_sim *sim);
+void ofono_sim_proactive_command_notify(struct ofono_sim *sim,
+ int length, const guint8 *pdu);
/* This will queue an operation to read all available records with id from the
* SIM. Callback cb will be called every time a record has been read, or once
diff --git a/plugins/atgen.c b/plugins/atgen.c
index 7e760bc..bbfb99c 100644
--- a/plugins/atgen.c
+++ b/plugins/atgen.c
@@ -156,12 +156,15 @@ static int atgen_disable(struct ofono_modem *modem)
static void atgen_pre_sim(struct ofono_modem *modem)
{
GAtChat *chat = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", chat);
- ofono_sim_create(modem, 0, "atmodem", chat);
+ sim = ofono_sim_create(modem, 0, "atmodem", chat);
ofono_voicecall_create(modem, 0, "atmodem", chat);
+
+ ofono_sim_inserted(sim);
}
static void atgen_post_sim(struct ofono_modem *modem)
diff --git a/plugins/calypso.c b/plugins/calypso.c
index 5b28176..db02b94 100644
--- a/plugins/calypso.c
+++ b/plugins/calypso.c
@@ -431,12 +431,15 @@ static int calypso_disable(struct ofono_modem *modem)
static void calypso_pre_sim(struct ofono_modem *modem)
{
struct calypso_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("");
ofono_devinfo_create(modem, 0, "atmodem", data->dlcs[AUX_DLC]);
- ofono_sim_create(modem, 0, "atmodem", data->dlcs[AUX_DLC]);
+ sim = ofono_sim_create(modem, 0, "atmodem", data->dlcs[AUX_DLC]);
ofono_voicecall_create(modem, 0, "calypsomodem", data->dlcs[VOICE_DLC]);
+
+ ofono_sim_inserted(sim);
}
static void calypso_post_sim(struct ofono_modem *modem)
diff --git a/plugins/em770.c b/plugins/em770.c
index a5d87f7..df09a8f 100644
--- a/plugins/em770.c
+++ b/plugins/em770.c
@@ -172,12 +172,15 @@ static int em770_disable(struct ofono_modem *modem)
static void em770_pre_sim(struct ofono_modem *modem)
{
struct em770_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", data->chat);
- ofono_sim_create(modem, 0, "atmodem", data->chat);
+ sim = ofono_sim_create(modem, 0, "atmodem", data->chat);
ofono_voicecall_create(modem, 0, "atmodem", data->chat);
+
+ ofono_sim_inserted(sim);
}
static void em770_post_sim(struct ofono_modem *modem)
diff --git a/plugins/g1.c b/plugins/g1.c
index 81edfab..2a3d257 100644
--- a/plugins/g1.c
+++ b/plugins/g1.c
@@ -156,12 +156,15 @@ static int g1_disable(struct ofono_modem *modem)
static void g1_pre_sim(struct ofono_modem *modem)
{
GAtChat *chat = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("");
ofono_devinfo_create(modem, 0, "atmodem", chat);
- ofono_sim_create(modem, 0, "atmodem", chat);
+ sim = ofono_sim_create(modem, 0, "atmodem", chat);
ofono_voicecall_create(modem, 0, "atmodem", chat);
+
+ ofono_sim_inserted(sim);
}
static void g1_post_sim(struct ofono_modem *modem)
diff --git a/plugins/hso.c b/plugins/hso.c
index e682064..9bebb0d 100644
--- a/plugins/hso.c
+++ b/plugins/hso.c
@@ -198,11 +198,14 @@ static int hso_disable(struct ofono_modem *modem)
static void hso_pre_sim(struct ofono_modem *modem)
{
struct hso_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", data->control);
- ofono_sim_create(modem, 0, "atmodem", data->control);
+ sim = ofono_sim_create(modem, 0, "atmodem", data->control);
+
+ ofono_sim_inserted(sim);
}
static void hso_post_sim(struct ofono_modem *modem)
diff --git a/plugins/huawei.c b/plugins/huawei.c
index 41ad636..ee48498 100644
--- a/plugins/huawei.c
+++ b/plugins/huawei.c
@@ -163,12 +163,15 @@ static int huawei_disable(struct ofono_modem *modem)
static void huawei_pre_sim(struct ofono_modem *modem)
{
struct huawei_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", data->chat);
- ofono_sim_create(modem, 0, "atmodem", data->chat);
+ sim = ofono_sim_create(modem, 0, "atmodem", data->chat);
ofono_voicecall_create(modem, 0, "atmodem", data->chat);
+
+ ofono_sim_inserted(sim);
}
static void huawei_post_sim(struct ofono_modem *modem)
diff --git a/plugins/mbm.c b/plugins/mbm.c
index a164361..af0915e 100644
--- a/plugins/mbm.c
+++ b/plugins/mbm.c
@@ -272,12 +272,15 @@ static int mbm_disable(struct ofono_modem *modem)
static void mbm_pre_sim(struct ofono_modem *modem)
{
struct mbm_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", data->chat);
- ofono_sim_create(modem, 0, "atmodem", data->chat);
+ sim = ofono_sim_create(modem, 0, "atmodem", data->chat);
ofono_voicecall_create(modem, 0, "atmodem", data->chat);
+
+ ofono_sim_inserted(sim);
}
static void mbm_post_sim(struct ofono_modem *modem)
diff --git a/plugins/palmpre.c b/plugins/palmpre.c
index 049b9bd..7734d0d 100644
--- a/plugins/palmpre.c
+++ b/plugins/palmpre.c
@@ -170,13 +170,16 @@ static int palmpre_disable(struct ofono_modem *modem)
static void palmpre_pre_sim(struct ofono_modem *modem)
{
struct palmpre_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", data->chat);
- ofono_sim_create(modem, OFONO_VENDOR_QUALCOMM_MSM, "atmodem",
+ sim = ofono_sim_create(modem, OFONO_VENDOR_QUALCOMM_MSM, "atmodem",
data->chat);
ofono_voicecall_create(modem, 0, "atmodem", data->chat);
+
+ ofono_sim_inserted(sim);
}
static void palmpre_post_sim(struct ofono_modem *modem)
diff --git a/plugins/phonesim.c b/plugins/phonesim.c
index 523f5a9..fb85523 100644
--- a/plugins/phonesim.c
+++ b/plugins/phonesim.c
@@ -277,16 +277,19 @@ static int phonesim_disable(struct ofono_modem *modem)
static void phonesim_pre_sim(struct ofono_modem *modem)
{
struct phonesim_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", data->chat);
- ofono_sim_create(modem, 0, "atmodem", data->chat);
+ sim = ofono_sim_create(modem, 0, "atmodem", data->chat);
if (data->calypso)
ofono_voicecall_create(modem, 0, "calypsomodem", data->chat);
else
ofono_voicecall_create(modem, 0, "atmodem", data->chat);
+
+ ofono_sim_inserted(sim);
}
static void phonesim_post_sim(struct ofono_modem *modem)
diff --git a/plugins/ste.c b/plugins/ste.c
index 66065c1..24eabba 100644
--- a/plugins/ste.c
+++ b/plugins/ste.c
@@ -208,12 +208,15 @@ static int ste_disable(struct ofono_modem *modem)
static void ste_pre_sim(struct ofono_modem *modem)
{
struct ste_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", data->chat);
- ofono_sim_create(modem, 0, "atmodem", data->chat);
+ sim = ofono_sim_create(modem, 0, "atmodem", data->chat);
ofono_voicecall_create(modem, 0, "stemodem", data->chat);
+
+ ofono_sim_inserted(sim);
}
static void ste_post_sim(struct ofono_modem *modem)
diff --git a/src/sim.c b/src/sim.c
index 6402761..3d89cb2 100644
--- a/src/sim.c
+++ b/src/sim.c
@@ -55,6 +55,7 @@ static gboolean sim_op_next(gpointer user_data);
static gboolean sim_op_retrieve_next(gpointer user);
static void sim_own_numbers_update(struct ofono_sim *sim);
static void sim_pin_check(struct ofono_sim *sim);
+static void sim_set_ready(struct ofono_sim *sim);
struct sim_file_op {
int id;
@@ -90,6 +91,8 @@ struct ofono_sim {
enum ofono_sim_cphs_phase cphs_phase;
unsigned char cphs_service_table[2];
struct ofono_watchlist *ready_watches;
+ struct ofono_watchlist *removed_watches;
+ gboolean inserted;
const struct ofono_sim_driver *driver;
void *driver_data;
struct ofono_atom *atom;
@@ -970,7 +973,7 @@ static void sim_cphs_information_read_cb(int ok, int length, int record,
memcpy(sim->cphs_service_table, data + 1, 2);
ready:
- ofono_sim_set_ready(sim);
+ sim_set_ready(sim);
}
static void sim_imsi_cb(const struct ofono_error *error, const char *imsi,
@@ -1745,6 +1748,60 @@ const unsigned char *ofono_sim_get_cphs_service_table(struct ofono_sim *sim)
return sim->cphs_service_table;
}
+void ofono_sim_inserted(struct ofono_sim *sim)
+{
+ sim->inserted = TRUE;
+
+ /* Perform SIM initialization according to 3GPP 31.102 Section 5.1.1.2
+ * The assumption here is that if sim manager is being initialized,
+ * then sim commands are implemented, and the sim manager is then
+ * responsible for checking the PIN, reading the IMSI and signaling
+ * SIM ready condition.
+ *
+ * The procedure according to 31.102 is roughly:
+ * Read EFecc
+ * Read EFli and EFpl
+ * SIM Pin check
+ * Request SIM phase (only in 51.011)
+ * Read EFust
+ * Read EFest
+ * Read IMSI
+ *
+ * At this point we signal the SIM ready condition and allow
+ * arbitrary files to be written or read, assuming their presence
+ * in the EFust
+ */
+ sim_determine_phase(sim);
+}
+
+void ofono_sim_removed(struct ofono_sim *sim)
+{
+ GSList *l;
+ ofono_sim_ready_notify_cb_t notify;
+
+ if (sim == NULL)
+ return;
+
+ sim->inserted = FALSE;
+
+ if (sim->ready == FALSE)
+ return;
+
+ sim->ready = FALSE;
+
+ for (l = sim->removed_watches->items; l; l = l->next) {
+ struct ofono_watchlist_item *item = l->data;
+ notify = item->notify;
+
+ notify(item->notify_data);
+ }
+}
+
+void ofono_sim_proactive_command_notify(struct ofono_sim *sim,
+ int length, const guint8 *pdu)
+{
+}
+
unsigned int ofono_sim_add_ready_watch(struct ofono_sim *sim,
ofono_sim_ready_notify_cb_t notify,
void *data, ofono_destroy_func destroy)
@@ -1773,6 +1830,34 @@ void ofono_sim_remove_ready_watch(struct ofono_sim *sim, unsigned int id)
__ofono_watchlist_remove_item(sim->ready_watches, id);
}
+unsigned int ofono_sim_add_removed_watch(struct ofono_sim *sim,
+ ofono_sim_ready_notify_cb_t notify,
+ void *data, ofono_destroy_func destroy)
+{
+ struct ofono_watchlist_item *item;
+
+ DBG("%p", sim);
+
+ if (sim == NULL)
+ return 0;
+
+ if (notify == NULL)
+ return 0;
+
+ item = g_new0(struct ofono_watchlist_item, 1);
+
+ item->notify = notify;
+ item->destroy = destroy;
+ item->notify_data = data;
+
+ return __ofono_watchlist_add_item(sim->removed_watches, item);
+}
+
+void ofono_sim_remove_removed_watch(struct ofono_sim *sim, unsigned int id)
+{
+ __ofono_watchlist_remove_item(sim->removed_watches, id);
+}
+
int ofono_sim_get_ready(struct ofono_sim *sim)
{
if (sim == NULL)
@@ -1784,7 +1869,7 @@ int ofono_sim_get_ready(struct ofono_sim *sim)
return 0;
}
-void ofono_sim_set_ready(struct ofono_sim *sim)
+static void sim_set_ready(struct ofono_sim *sim)
{
GSList *l;
ofono_sim_ready_notify_cb_t notify;
@@ -1869,6 +1954,8 @@ static void sim_unregister(struct ofono_atom *atom)
__ofono_watchlist_free(sim->ready_watches);
sim->ready_watches = NULL;
+ __ofono_watchlist_free(sim->removed_watches);
+ sim->removed_watches = NULL;
g_dbus_unregister_interface(conn, path, OFONO_SIM_MANAGER_INTERFACE);
ofono_modem_remove_interface(modem, OFONO_SIM_MANAGER_INTERFACE);
@@ -1983,31 +2070,11 @@ void ofono_sim_register(struct ofono_sim *sim)
ofono_modem_add_interface(modem, OFONO_SIM_MANAGER_INTERFACE);
sim->ready_watches = __ofono_watchlist_new(g_free);
+ sim->removed_watches = __ofono_watchlist_new(g_free);
__ofono_atom_register(sim->atom, sim_unregister);
ofono_sim_add_ready_watch(sim, sim_ready, sim, NULL);
-
- /* Perform SIM initialization according to 3GPP 31.102 Section 5.1.1.2
- * The assumption here is that if sim manager is being initialized,
- * then sim commands are implemented, and the sim manager is then
- * responsible for checking the PIN, reading the IMSI and signaling
- * SIM ready condition.
- *
- * The procedure according to 31.102 is roughly:
- * Read EFecc
- * Read EFli and EFpl
- * SIM Pin check
- * Request SIM phase (only in 51.011)
- * Read EFust
- * Read EFest
- * Read IMSI
- *
- * At this point we signal the SIM ready condition and allow
- * arbitrary files to be written or read, assuming their presence
- * in the EFust
- */
- sim_determine_phase(sim);
}
void ofono_sim_remove(struct ofono_sim *sim)
--
1.6.1
10 years, 10 months
[PATCH] Return network's USSD reponses from the Respond method instead of signalling
by Andrzej Zaborowski
---
doc/supplementaryservices-api.txt | 2 +-
src/ussd.c | 31 ++++++++++++++++++++++++-------
2 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/doc/supplementaryservices-api.txt b/doc/supplementaryservices-api.txt
index 23796c1..115e2ff 100644
--- a/doc/supplementaryservices-api.txt
+++ b/doc/supplementaryservices-api.txt
@@ -16,7 +16,7 @@ Methods string, variant Initiate(string command)
new command can be initiated until this one is
cancelled or ended.
- void Respond(string reply)
+ string Respond(string reply)
Send a response to the network either when
it is awaiting further input after Initiate()
diff --git a/src/ussd.c b/src/ussd.c
index a2a4f5d..9ec7600 100644
--- a/src/ussd.c
+++ b/src/ussd.c
@@ -360,8 +360,20 @@ void ofono_ussd_notify(struct ofono_ussd *ussd, int status, const char *str)
else
ussd_change_state(ussd, USSD_STATE_IDLE);
- } else if (ussd->state == USSD_STATE_IDLE ||
- ussd->state == USSD_STATE_RESPONSE_SENT) {
+ } else if (ussd->state == USSD_STATE_RESPONSE_SENT) {
+ reply = dbus_message_new_method_return(ussd->pending);
+
+ if (!str)
+ str = "";
+
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &str,
+ DBUS_TYPE_INVALID);
+
+ if (status == OFONO_USSD_STATUS_ACTION_REQUIRED)
+ ussd_change_state(ussd, USSD_STATE_USER_ACTION);
+ else
+ ussd_change_state(ussd, USSD_STATE_IDLE);
+ } else if (ussd->state == USSD_STATE_IDLE) {
const char *signal_name;
const char *path = __ofono_atom_get_path(ussd->atom);
int new_state;
@@ -462,14 +474,19 @@ static void ussd_response_callback(const struct ofono_error *error, void *data)
struct ofono_ussd *ussd = data;
DBusMessage *reply;
+ if (error->type != OFONO_ERROR_TYPE_NO_ERROR)
+ DBG("ussd response failed with error: %s",
+ telephony_error_to_str(error));
+
if (error->type == OFONO_ERROR_TYPE_NO_ERROR) {
ussd_change_state(ussd, USSD_STATE_RESPONSE_SENT);
- reply = dbus_message_new_method_return(ussd->pending);
- } else {
- ussd_change_state(ussd, USSD_STATE_IDLE);
- reply = __ofono_error_failed(ussd->pending);
+ return;
}
+ if (!ussd->pending)
+ return;
+
+ reply = __ofono_error_failed(ussd->pending);
__ofono_dbus_pending_reply(&ussd->pending, reply);
}
@@ -575,7 +592,7 @@ static DBusMessage *ussd_get_properties(DBusConnection *conn,
static GDBusMethodTable ussd_methods[] = {
{ "Initiate", "s", "sv", ussd_initiate,
G_DBUS_METHOD_FLAG_ASYNC },
- { "Respond", "s", "", ussd_respond,
+ { "Respond", "s", "s", ussd_respond,
G_DBUS_METHOD_FLAG_ASYNC },
{ "Cancel", "", "", ussd_cancel,
G_DBUS_METHOD_FLAG_ASYNC },
--
1.6.1
10 years, 11 months
[PATCH 5/5] Implement STATUS polling in atmodem driver, enable it for phonesim & atgen.
by Andrzej Zaborowski
---
Makefile.am | 2 +
drivers/atmodem/sim-poll.c | 283 ++++++++++++++++++++++++++++++++++++++++++++
drivers/atmodem/sim-poll.h | 22 ++++
plugins/atgen.c | 7 +-
plugins/phonesim.c | 6 +-
5 files changed, 316 insertions(+), 4 deletions(-)
create mode 100644 drivers/atmodem/sim-poll.c
create mode 100644 drivers/atmodem/sim-poll.h
diff --git a/Makefile.am b/Makefile.am
index 0eaadda..0df918e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -123,6 +123,8 @@ builtin_sources += $(gatchat_sources) \
drivers/atmodem/call-meter.c \
drivers/atmodem/network-registration.c \
drivers/atmodem/sim.c \
+ drivers/atmodem/sim-poll.c \
+ drivers/atmodem/sim-poll.h \
drivers/atmodem/ussd.c \
drivers/atmodem/voicecall.c \
drivers/atmodem/call-barring.c \
diff --git a/drivers/atmodem/sim-poll.c b/drivers/atmodem/sim-poll.c
new file mode 100644
index 0000000..303ea82
--- /dev/null
+++ b/drivers/atmodem/sim-poll.c
@@ -0,0 +1,283 @@
+/*
+ *
+ * oFono - Open Source Telephony
+ *
+ * Copyright (C) 2008-2010 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#include <ofono/modem.h>
+#include <ofono/sim.h>
+
+#include "gatchat.h"
+#include "gatresult.h"
+#include "ofono.h"
+
+#include "atmodem.h"
+#include "sim-poll.h"
+
+struct sim_poll_data {
+ GAtChat *chat;
+ struct ofono_modem *modem;
+ struct ofono_sim *sim;
+ unsigned int sim_watch;
+ unsigned int sim_ready_watch;
+ unsigned int sim_removed_watch;
+ gboolean inserted;
+ int idle_poll_interval;
+ gint status_timeout;
+ gint poll_timeout;
+ guint status_cmd;
+};
+
+static const char *csim_prefix[] = { "+CSIM:", NULL };
+
+static gboolean sim_status_poll(gpointer user_data);
+static void sim_fetch_command(struct sim_poll_data *spd, int length);
+
+static void at_csim_fetch_cb(gboolean ok, GAtResult *result,
+ gpointer user_data)
+{
+ struct sim_poll_data *spd = user_data;
+ GAtResultIter iter;
+ const guint8 *response;
+ gint rlen, len;
+
+ if (!ok)
+ return;
+
+ g_at_result_iter_init(&iter, result);
+
+ if (!g_at_result_iter_next(&iter, "+CSIM:"))
+ return;
+
+ if (!g_at_result_iter_next_number(&iter, &rlen))
+ return;
+
+ if (!g_at_result_iter_next_hexstring(&iter, &response, &len))
+ return;
+
+ if (rlen != len * 2 || len < 2 || (response[len - 2] != 0x90 &&
+ response[len - 2] != 0x91) ||
+ (response[len - 2] == 0x90 && response[len - 1] != 0))
+ return;
+
+ DBG("csim_fetch_cb: %i", len);
+
+ ofono_sim_proactive_command_notify(spd->sim, len - 2, response);
+
+ /* Can this happen? */
+ if (response[len - 2] == 0x91)
+ sim_fetch_command(spd, response[len - 1]);
+}
+
+static void sim_fetch_command(struct sim_poll_data *spd, int length)
+{
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "AT+CSIM=10,A0120000%02hhX", length);
+
+ g_at_chat_send(spd->chat, buf, csim_prefix,
+ at_csim_fetch_cb, spd, NULL);
+}
+
+static void sim_status_poll_schedule(struct sim_poll_data *spd)
+{
+ /* TODO: Decide on the interval based on whether any call is active */
+ /* TODO: On idle, possibly only schedule if proactive commands enabled
+ * as indicated by EFphase + EFsst (51.011: 11.6.1) */
+ int interval = spd->idle_poll_interval;
+
+ /* When a SIM is inserted, the SIM might have requested a different
+ * interval. */
+ if (spd->inserted)
+ interval = ofono_modem_get_integer(spd->modem,
+ "status-poll-interval");
+
+ spd->poll_timeout = g_timeout_add_seconds(interval,
+ sim_status_poll, spd);
+}
+
+static gboolean sim_status_timeout(gpointer user_data)
+{
+ struct sim_poll_data *spd = user_data;
+
+ spd->status_timeout = 0;
+
+ g_at_chat_cancel(spd->chat, spd->status_cmd);
+ spd->status_cmd = 0;
+
+ if (spd->inserted == TRUE) {
+ spd->inserted = FALSE;
+ ofono_sim_removed(spd->sim);
+ }
+
+ sim_status_poll_schedule(spd);
+
+ return FALSE;
+}
+
+static void at_csim_status_cb(gboolean ok, GAtResult *result,
+ gpointer user_data)
+{
+ struct sim_poll_data *spd = user_data;
+ GAtResultIter iter;
+ const guint8 *response;
+ gint rlen, len;
+
+ spd->status_cmd = 0;
+
+ if (!spd->status_timeout)
+ /* The STATUS already timed out */
+ return;
+
+ /* Card responded on time */
+
+ g_source_remove(spd->status_timeout);
+ spd->status_timeout = 0;
+
+ if (spd->inserted != TRUE) {
+ spd->inserted = TRUE;
+ ofono_sim_inserted(spd->sim);
+ }
+
+ sim_status_poll_schedule(spd);
+
+ /* Check if we have a proactive command */
+
+ if (!ok)
+ return;
+
+ g_at_result_iter_init(&iter, result);
+
+ if (!g_at_result_iter_next(&iter, "+CSIM:"))
+ return;
+
+ if (!g_at_result_iter_next_number(&iter, &rlen))
+ return;
+
+ if (!g_at_result_iter_next_hexstring(&iter, &response, &len))
+ return;
+
+ if (rlen != len * 2 || len < 2 || response[len - 2] != 0x91)
+ return;
+
+ /* We have a proactive command pending, FETCH it */
+ sim_fetch_command(spd, response[len - 1]);
+}
+
+static gboolean sim_status_poll(gpointer user_data)
+{
+ struct sim_poll_data *spd = user_data;
+
+ spd->poll_timeout = 0;
+
+ /* The SIM must respond in a given time frame which is of at
+ * least 5 seconds in TS 11.11. */
+ spd->status_timeout = g_timeout_add_seconds(5,
+ sim_status_timeout, spd);
+
+ /* Send STATUS */
+ spd->status_cmd = g_at_chat_send(spd->chat, "AT+CSIM=8,A0F200C0",
+ csim_prefix, at_csim_status_cb, spd, NULL);
+ if (spd->status_cmd == 0)
+ at_csim_status_cb(FALSE, NULL, spd);
+
+ return FALSE;
+}
+
+static void sim_ready_watch(void *user)
+{
+ struct sim_poll_data *spd = user;
+
+ spd->inserted = TRUE;
+
+ ofono_modem_set_integer(spd->modem, "status-poll-interval", 30);
+}
+
+static void sim_removed_watch(void *user)
+{
+ struct sim_poll_data *spd = user;
+
+ spd->inserted = FALSE;
+}
+
+static void sim_watch(struct ofono_atom *atom,
+ enum ofono_atom_watch_condition cond, void *data)
+{
+ struct sim_poll_data *spd = data;
+
+ if (cond != OFONO_ATOM_WATCH_CONDITION_UNREGISTERED)
+ return;
+
+ spd->sim_ready_watch = 0;
+ spd->sim_removed_watch = 0;
+
+ if (spd->sim_watch) {
+ __ofono_modem_remove_atom_watch(spd->modem, spd->sim_watch);
+ spd->sim_watch = 0;
+ }
+
+ if (spd->status_timeout) {
+ g_source_remove(spd->status_timeout);
+ spd->status_timeout = 0;
+ }
+
+ if (spd->poll_timeout) {
+ g_source_remove(spd->poll_timeout);
+ spd->poll_timeout = 0;
+ }
+
+ g_free(spd);
+}
+
+void ofono_atmodem_poll_enable(struct ofono_modem *modem, void *data)
+{
+ struct ofono_atom *sim_atom;
+ struct sim_poll_data *spd;
+
+ sim_atom = __ofono_modem_find_atom(modem, OFONO_ATOM_TYPE_SIM);
+ if (!sim_atom || !__ofono_atom_get_registered(sim_atom))
+ return;
+
+ spd = g_new0(struct sim_poll_data, 1);
+ spd->chat = data;
+ spd->modem = modem;
+ spd->sim = __ofono_atom_get_data(sim_atom);
+ spd->idle_poll_interval = 30;
+
+ spd->sim_ready_watch = ofono_sim_add_ready_watch(spd->sim,
+ sim_ready_watch, spd, NULL);
+ spd->sim_removed_watch = ofono_sim_add_removed_watch(spd->sim,
+ sim_removed_watch, spd, NULL);
+ spd->sim_watch = __ofono_modem_add_atom_watch(spd->modem,
+ OFONO_ATOM_TYPE_SIM, sim_watch, spd, NULL);
+
+ if (ofono_sim_get_ready(spd->sim))
+ sim_ready_watch(spd);
+
+ sim_status_poll(spd);
+}
diff --git a/drivers/atmodem/sim-poll.h b/drivers/atmodem/sim-poll.h
new file mode 100644
index 0000000..b8f9c5c
--- /dev/null
+++ b/drivers/atmodem/sim-poll.h
@@ -0,0 +1,22 @@
+/*
+ *
+ * oFono - Open Source Telephony
+ *
+ * Copyright (C) 2008-2010 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+void ofono_atmodem_poll_enable(struct ofono_modem *modem, void *data);
diff --git a/plugins/atgen.c b/plugins/atgen.c
index bbfb99c..3501675 100644
--- a/plugins/atgen.c
+++ b/plugins/atgen.c
@@ -48,6 +48,8 @@
#include <ofono/ussd.h>
#include <ofono/voicecall.h>
+#include <drivers/atmodem/sim-poll.h>
+
static const char *tty_opts[] = {
"Baud",
"Read",
@@ -156,15 +158,14 @@ static int atgen_disable(struct ofono_modem *modem)
static void atgen_pre_sim(struct ofono_modem *modem)
{
GAtChat *chat = ofono_modem_get_data(modem);
- struct ofono_sim *sim;
DBG("%p", modem);
ofono_devinfo_create(modem, 0, "atmodem", chat);
- sim = ofono_sim_create(modem, 0, "atmodem", chat);
+ ofono_sim_create(modem, 0, "atmodem", chat);
ofono_voicecall_create(modem, 0, "atmodem", chat);
- ofono_sim_inserted(sim);
+ ofono_atmodem_poll_enable(modem, chat);
}
static void atgen_post_sim(struct ofono_modem *modem)
diff --git a/plugins/phonesim.c b/plugins/phonesim.c
index fb85523..7ad2cb6 100644
--- a/plugins/phonesim.c
+++ b/plugins/phonesim.c
@@ -58,6 +58,7 @@
#include <ofono/gprs-context.h>
#include <drivers/atmodem/vendor.h>
+#include <drivers/atmodem/sim-poll.h>
struct phonesim_data {
GAtMux *mux;
@@ -289,7 +290,10 @@ static void phonesim_pre_sim(struct ofono_modem *modem)
else
ofono_voicecall_create(modem, 0, "atmodem", data->chat);
- ofono_sim_inserted(sim);
+ if (data->calypso)
+ ofono_sim_inserted(sim);
+ else
+ ofono_atmodem_poll_enable(modem, data->chat);
}
static void phonesim_post_sim(struct ofono_modem *modem)
--
1.6.1
10 years, 11 months
[PATCH] Fix ampersand commands check
by Andrzej Zaborowski
Also remove a trailing tab.
---
gatchat/gatserver.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/gatchat/gatserver.c b/gatchat/gatserver.c
index 031e542..839ebe4 100644
--- a/gatchat/gatserver.c
+++ b/gatchat/gatserver.c
@@ -570,9 +570,9 @@ static int get_basic_prefix_size(const char *buf)
/* All other cases it is a simple 1 character prefix */
return 1;
}
-
+
if (buf[0] == '&') {
- if (g_ascii_isalpha(buf[0] == FALSE))
+ if (g_ascii_isalpha(buf[1]) == FALSE)
return 0;
return 2;
--
1.6.1
10 years, 11 months