From 6e3b494227b89790ab0e0de61f12d87a9cd66396 Mon Sep 17 00:00:00 2001
From: Yang Gu <yang.gu@intel.com>
Date: Fri, 11 Sep 2009 14:59:32 +0800
Subject: [PATCH] Read Preferred PLMN list

---
 drivers/atmodem/network-registration.c |  120 ++++++++++++++++++++++++++++++++
 include/netreg.h                       |   18 ++++-
 src/network.c                          |   50 +++++++++++++
 3 files changed, 185 insertions(+), 3 deletions(-)

diff --git a/drivers/atmodem/network-registration.c b/drivers/atmodem/network-registration.c
index 43a3f7d..69613dc 100644
--- a/drivers/atmodem/network-registration.c
+++ b/drivers/atmodem/network-registration.c
@@ -44,6 +44,8 @@ static const char *none_prefix[] = { NULL };
 static const char *creg_prefix[] = { "+CREG:", NULL };
 static const char *cops_prefix[] = { "+COPS:", NULL };
 static const char *csq_prefix[] = { "+CSQ:", NULL };
+static const char *cpol_prefix[] = { "+CPOL:", NULL };
+
 
 struct netreg_data {
 	GAtChat *chat;
@@ -608,6 +610,123 @@ error:
 	}
 }
 
+static void at_list_pref_oper_cb(gboolean ok, GAtResult *result,
+				gpointer user_data)
+{
+	struct cb_data *cbd = user_data;
+	ofono_netreg_pref_oper_list_cb_t cb = cbd->cb;
+	GAtResultIter iter;
+	struct ofono_error error;
+	int num = 0;
+	int i;
+	struct ofono_preferred_operator *list = NULL;
+
+	decode_at_error(&error, g_at_result_final_response(result));
+	if (!ok)
+		goto error;
+
+	g_at_result_iter_init(&iter, result);
+	while (g_at_result_iter_next(&iter, "+CPOL:"))
+		num += 1;
+	if (num < 1)
+		goto error;
+
+	list = g_new(struct ofono_preferred_operator, num);
+	g_at_result_iter_init(&iter, result);
+	for (num = 0; g_at_result_iter_next(&iter, "+CPOL:"); num++) {
+		int format;
+		const char *oper_temp = NULL;
+
+		g_at_result_iter_skip_next(&iter);
+
+		g_at_result_iter_next_number(&iter, &format);
+		g_assert(format == 2);
+
+		if (!g_at_result_iter_next_string(&iter, &oper_temp))
+			continue;
+		list[num].mccmnc = g_strdup(oper_temp);
+	}
+
+	cb(&error, num, list, cbd->data);
+
+	for (i = 0; i < num; i++)
+		g_free(list[i].mccmnc);
+	g_free(list);
+
+	return;
+
+error:
+	cb(&error, 0, NULL, cbd->data);
+	return;
+}
+
+static void at_change_oper_format_cb(gboolean ok, GAtResult *result,
+						gpointer user_data)
+{
+	struct cb_data *cbd = user_data;
+	struct ofono_netreg *netreg = cbd->user;
+	struct netreg_data *nd = ofono_netreg_get_data(netreg);
+
+	if (!ok)
+		goto error;
+
+	if (g_at_chat_send(nd->chat, "AT+CPOL?", cpol_prefix,
+				at_list_pref_oper_cb, cbd, g_free) > 0)
+		return;
+
+ error:
+	{
+		ofono_netreg_pref_oper_list_cb_t cb = cbd->cb;
+		DECLARE_FAILURE(error);
+		cb(&error, 0, NULL, cbd->data);
+	}
+}
+
+static void at_select_plmn_cb(gboolean ok, GAtResult *result,
+						gpointer user_data)
+{
+	struct cb_data *cbd = user_data;
+	struct ofono_netreg *netreg = cbd->user;
+	struct netreg_data *nd = ofono_netreg_get_data(netreg);
+
+	if (!ok)
+		goto error;
+
+	if (g_at_chat_send(nd->chat, "AT+CPOL=,2", none_prefix,
+				at_change_oper_format_cb, cbd, NULL) > 0)
+		return;
+
+ error:
+	{
+		ofono_netreg_pref_oper_list_cb_t cb = cbd->cb;
+		DECLARE_FAILURE(error);
+		cb(&error, 0, NULL, cbd->data);
+	}
+}
+
+static void at_list_pref_oper(struct ofono_netreg *netreg,
+				ofono_netreg_pref_oper_list_cb_t cb, void *data)
+{
+	struct netreg_data *nd = ofono_netreg_get_data(netreg);
+	struct cb_data *cbd = cb_data_new(cb, data);
+
+	if (!cbd)
+		goto error;
+
+	cbd->user = netreg;
+	if (g_at_chat_send(nd->chat, "AT+CPLS=0", none_prefix,
+				at_select_plmn_cb, cbd, NULL) > 0)
+		return;
+error:
+	if (cbd)
+		g_free(cbd);
+
+	{
+		DECLARE_FAILURE(error);
+		cb(&error, 0, NULL, data);
+	}
+}
+
 static void creg_notify(GAtResult *result, gpointer user_data)
 {
 	struct ofono_netreg *netreg = user_data;
@@ -706,6 +825,7 @@ static struct ofono_netreg_driver driver = {
 	.register_manual		= at_register_manual,
 	.deregister			= at_deregister,
 	.strength			= at_signal_strength,
+	.list_pref_oper			= at_list_pref_oper,
 };
 
 void at_netreg_init()
diff --git a/include/netreg.h b/include/netreg.h
index 9e99200..107728e 100644
--- a/include/netreg.h
+++ b/include/netreg.h
@@ -43,6 +43,10 @@ struct ofono_network_operator {
 	int tech;
 };
 
+struct ofono_preferred_operator {
+	char *mccmnc;
+};
+
 typedef void (*ofono_netreg_operator_cb_t)(const struct ofono_error *error,
 					const struct ofono_network_operator *op,
 					void *data);
@@ -51,9 +55,9 @@ typedef void (*ofono_netreg_register_cb_t)(const struct ofono_error *error,
 					void *data);
 
 typedef void (*ofono_netreg_operator_list_cb_t)(const struct ofono_error *error,
-					int total,
-					const struct ofono_network_operator *list,
-					void *data);
+				int total,
+				const struct ofono_network_operator *list,
+				void *data);
 
 typedef void (*ofono_netreg_status_cb_t)(const struct ofono_error *error,
 					int status, int lac, int ci, int tech,
@@ -62,6 +66,12 @@ typedef void (*ofono_netreg_status_cb_t)(const struct ofono_error *error,
 typedef void (*ofono_netreg_strength_cb_t)(const struct ofono_error *error,
 					int strength, void *data);
 
+typedef void (*ofono_netreg_pref_oper_list_cb_t)(
+				const struct ofono_error *error,
+				int total,
+				const struct ofono_preferred_operator *list,
+				void *data);
+
 /* Network related functions, including registration status, operator selection
  * and signal strength indicators.
  *
@@ -88,6 +98,8 @@ struct ofono_netreg_driver {
 			ofono_netreg_register_cb_t cb, void *data);
 	void (*strength)(struct ofono_netreg *netreg,
 			ofono_netreg_strength_cb_t, void *data);
+	void (*list_pref_oper)(struct ofono_netreg *netreg,
+			ofono_netreg_pref_oper_list_cb_t cb, void *data);
 };
 
 void ofono_netreg_strength_notify(struct ofono_netreg *netreg, int strength);
diff --git a/src/network.c b/src/network.c
index 7139e60..1aac668 100644
--- a/src/network.c
+++ b/src/network.c
@@ -80,6 +80,7 @@ struct ofono_netreg {
 	const struct ofono_netreg_driver *driver;
 	void *driver_data;
 	struct ofono_atom *atom;
+	GSList *pref_oper_list;
 };
 
 static void operator_list_callback(const struct ofono_error *error, int total,
@@ -658,6 +659,9 @@ static DBusMessage *network_get_properties(DBusConnection *conn,
 	DBusMessage *reply;
 	DBusMessageIter iter;
 	DBusMessageIter dict;
+	char **list;
+	GSList *l;
+	int i;
 
 	const char *status = registration_status_to_string(netreg->status);
 	const char *operator;
@@ -713,6 +717,16 @@ static DBusMessage *network_get_properties(DBusConnection *conn,
 					&strength);
 	}
 
+	/* Preferred networks */
+	list = g_new0(char *, g_slist_length(netreg->pref_oper_list) + 1);
+
+	for (i = 0, l = netreg->pref_oper_list; l; l = l->next, i++)
+		list[i] = g_strdup(l->data);
+
+	ofono_dbus_dict_append_array(&dict, "PreferredNetworks",
+					DBUS_TYPE_STRING, &list);
+	g_strfreev(list);
+
 	dbus_message_iter_close_container(&iter, &dict);
 
 	return reply;
@@ -1395,6 +1409,12 @@ static void netreg_remove(struct ofono_atom *atom)
 	if (netreg->spname)
 		g_free(netreg->spname);
 
+	if (netreg->pref_oper_list) {
+		g_slist_foreach(netreg->pref_oper_list, (GFunc)g_free, NULL);
+		g_slist_free(netreg->pref_oper_list);
+		netreg->pref_oper_list = NULL;
+	}
+
 	g_free(netreg);
 }
 
@@ -1439,6 +1459,33 @@ struct ofono_netreg *ofono_netreg_create(struct ofono_modem *modem,
 	return netreg;
 }
 
+static gboolean in_pref_oper_list(char *mccmnc, GSList *list)
+{
+	GSList *l;
+	for (l = list; l; l = l->next)
+		if (!strcmp(mccmnc, l->data))
+			return TRUE;
+	return FALSE;
+}
+
+static void list_pref_oper_cb(const struct ofono_error *error,
+			int total,
+			const struct ofono_preferred_operator *list,
+			void *userdata)
+{
+	struct ofono_netreg *netreg = userdata;
+	int i;
+
+	for (i = 0; i < total; i++) {
+		if (!in_pref_oper_list(list[i].mccmnc, netreg->pref_oper_list))
+			netreg->pref_oper_list =
+				g_slist_prepend(netreg->pref_oper_list,
+						g_strdup(list[i].mccmnc));
+	}
+
+	netreg->pref_oper_list = g_slist_reverse(netreg->pref_oper_list);
+}
+
 static void netreg_sim_ready(void *userdata)
 {
 	struct ofono_netreg *netreg = userdata;
@@ -1454,6 +1501,9 @@ static void netreg_sim_ready(void *userdata)
 		netreg->driver->registration_status(netreg,
 					init_registration_status, netreg);
 
+	if (netreg->driver->list_pref_oper)
+		netreg->driver->list_pref_oper(netreg,
+					list_pref_oper_cb, netreg);
 }
 
 static void netreg_sim_watch(struct ofono_atom *atom,
-- 
1.6.0.6

