diff --git a/js/misc/modemManager.js b/js/misc/modemManager.js index 35b80b1e7..6de24d92b 100644 --- a/js/misc/modemManager.js +++ b/js/misc/modemManager.js @@ -46,8 +46,70 @@ let _providersTable; function _getProvidersTable() { if (_providersTable) return _providersTable; - let [providers, countryCodes] = Shell.mobile_providers_parse(); - return _providersTable = providers; + return _providersTable = Shell.mobile_providers_parse(null,null); +} + +function findProviderForMCCMNC(table, needle) { + let needlemcc = needle.substring(0, 3); + let needlemnc = needle.substring(3, needle.length); + + let name2, name3; + for (let iter in table) { + let country = table[iter]; + let providers = country.get_providers(); + + // Search through each country's providers + for (let i = 0; i < providers.length; i++) { + let provider = providers[i]; + + // Search through MCC/MNC list + let list = provider.get_gsm_mcc_mnc(); + for (let j = 0; j < list.length; j++) { + let mccmnc = list[j]; + + // Match both 2-digit and 3-digit MNC; prefer a + // 3-digit match if found, otherwise a 2-digit one. + if (mccmnc.mcc != needlemcc) + continue; // MCC was wrong + + if (!name3 && needle.length == 6 && needlemnc == mccmnc.mnc) + name3 = provider.name; + + if (!name2 && needlemnc.substring(0, 2) == mccmnc.mnc.substring(0, 2)) + name2 = provider.name; + + if (name2 && name3) + break; + } + } + } + + return name3 || name2 || null; +} + +function findProviderForSid(table, sid) { + if (sid == 0) + return null; + + // Search through each country + for (let iter in table) { + let country = table[iter]; + let providers = country.get_providers(); + + // Search through each country's providers + for (let i = 0; i < providers.length; i++) { + let provider = providers[i]; + let cdma_sid = provider.get_cdma_sid(); + + // Search through CDMA SID list + for (let j = 0; j < cdma_sid.length; j++) { + if (cdma_sid[j] == sid) + return provider.name; + } + } + } + + return null; } const ModemGsm = new Lang.Class({ @@ -91,64 +153,29 @@ const ModemGsm = new Lang.Class({ }, _findOperatorName: function(name, opCode) { - if (name.length != 0 && (name.length > 6 || name.length < 5)) { - // this looks like a valid name, i.e. not an MCCMNC (that some - // devices return when not yet connected - return name; - } - if (isNaN(parseInt(name))) { - // name is definitely not a MCCMNC, so it may be a name - // after all; return that - return name; + if (name) { + if (name && name.length != 0 && (name.length > 6 || name.length < 5)) { + // this looks like a valid name, i.e. not an MCCMNC (that some + // devices return when not yet connected + return name; + } + if (isNaN(parseInt(name))) { + // name is definitely not a MCCMNC, so it may be a name + // after all; return that + return name; + } } let needle; - if (name.length == 0 && opCode) + if ((name == null || name.length == 0) && opCode) needle = opCode; else if (name.length == 6 || name.length == 5) needle = name; else // nothing to search return null; - return this._findProviderForMCCMNC(needle); - }, - - _findProviderForMCCMNC: function(needle) { let table = _getProvidersTable(); - let needlemcc = needle.substring(0, 3); - let needlemnc = needle.substring(3, needle.length); - - let name2, name3; - for (let iter in table) { - let providers = table[iter]; - - // Search through each country's providers - for (let i = 0; i < providers.length; i++) { - let provider = providers[i]; - - // Search through MCC/MNC list - let list = provider.get_gsm_mcc_mnc(); - for (let j = 0; j < list.length; j++) { - let mccmnc = list[j]; - - // Match both 2-digit and 3-digit MNC; prefer a - // 3-digit match if found, otherwise a 2-digit one. - if (mccmnc.mcc != needlemcc) - continue; // MCC was wrong - - if (!name3 && needle.length == 6 && needlemnc == mccmnc.mnc) - name3 = provider.name; - - if (!name2 && needlemnc.substring(0, 2) == mccmnc.mnc.substring(0, 2)) - name2 = provider.name; - - if (name2 && name3) - break; - } - } - } - - return name3 || name2 || null; + return findProviderForMCCMNC(table, needle); } }); Signals.addSignalMethods(ModemGsm.prototype); @@ -189,39 +216,14 @@ const ModemCdma = new Lang.Class({ this.operator_name = null; } else { let [bandClass, band, id] = result; - if (name.length > 0) - this.operator_name = this._findProviderForSid(id); - else + if (name.length > 0) { + let table = _getProvidersTable(); + this.operator_name = findProviderForSid(table, id); + } else this.operator_name = null; } this.emit('notify::operator-name'); })); - }, - - _findProviderForSid: function(sid) { - if (sid == 0) - return null; - - let table = _getProvidersTable(); - - // Search through each country - for (let iter in table) { - let providers = table[iter]; - - // Search through each country's providers - for (let i = 0; i < providers.length; i++) { - let provider = providers[i]; - let cdma_sid = provider.get_cdma_sid(); - - // Search through CDMA SID list - for (let j = 0; j < cdma_sid.length; j++) { - if (cdma_sid[j] == sid) - return provider.name; - } - } - } - - return null; } }); Signals.addSignalMethods(ModemCdma.prototype); diff --git a/src/shell-mobile-providers.c b/src/shell-mobile-providers.c index 13a8b3ece..9901d8bdd 100644 --- a/src/shell-mobile-providers.c +++ b/src/shell-mobile-providers.c @@ -37,10 +37,11 @@ #define ISO_3166_COUNTRY_CODES DATADIR "/zoneinfo/iso3166.tab" - +static ShellCountryMobileProvider *country_mobile_provider_new (const char *country_code, + const gchar *country_name); static GHashTable * -read_country_codes (void) +read_country_codes (const gchar *country_codes) { GHashTable *table; GIOChannel *channel; @@ -48,18 +49,21 @@ read_country_codes (void) GError *error = NULL; GIOStatus status; - channel = g_io_channel_new_file (ISO_3166_COUNTRY_CODES, "r", &error); + channel = g_io_channel_new_file (country_codes, "r", &error); if (!channel) { if (error) { - g_warning ("Could not read " ISO_3166_COUNTRY_CODES ": %s", error->message); + g_warning ("Could not read %s: %s", country_codes, error->message); g_error_free (error); } else - g_warning ("Could not read " ISO_3166_COUNTRY_CODES ": Unknown error"); + g_warning ("Could not read %s: Unknown error", country_codes); return NULL; } - table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + table = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify)shell_country_mobile_provider_unref); buffer = g_string_sized_new (32); status = G_IO_STATUS_NORMAL; @@ -69,6 +73,7 @@ read_country_codes (void) switch (status) { case G_IO_STATUS_NORMAL: if (buffer->str[0] != '#') { + ShellCountryMobileProvider *country_provider; char **pieces; pieces = g_strsplit (buffer->str, "\t", 2); @@ -80,7 +85,8 @@ read_country_codes (void) pieces[1] = g_strdup (_("United Kingdom")); } - g_hash_table_insert (table, pieces[0], pieces[1]); + country_provider = country_mobile_provider_new (pieces[0], pieces[1]); + g_hash_table_insert (table, pieces[0], country_provider); g_free (pieces); } @@ -117,7 +123,6 @@ typedef enum { } MobileContextState; typedef struct { - GHashTable *country_codes; GHashTable *table; char *current_country; @@ -276,6 +281,86 @@ shell_mobile_provider_get_type (void) return type; } +static ShellCountryMobileProvider * +country_mobile_provider_new (const char *country_code, + const gchar *country_name) +{ + ShellCountryMobileProvider *country_provider; + + country_provider = g_slice_new0 (ShellCountryMobileProvider); + country_provider->refs = 1; + country_provider->country_code = g_strdup (country_code); + country_provider->country_name = g_strdup (country_name); + return country_provider; +} + +ShellCountryMobileProvider * +shell_country_mobile_provider_ref (ShellCountryMobileProvider *country_provider) +{ + country_provider->refs++; + + return country_provider; +} + +void +shell_country_mobile_provider_unref (ShellCountryMobileProvider *country_provider) +{ + if (--country_provider->refs == 0) { + g_free (country_provider->country_code); + g_free (country_provider->country_name); + g_slist_free_full (country_provider->providers, + (GDestroyNotify) shell_mobile_provider_unref); + g_slice_free (ShellCountryMobileProvider, country_provider); + } +} + +/** + * shell_country_mobile_provider_get_country_code: + * + * Returns: (transfer none): the code of the country. + */ +const gchar * +shell_country_mobile_provider_get_country_code (ShellCountryMobileProvider *country_provider) +{ + return country_provider->country_code; +} + +/** + * shell_country_mobile_provider_get_country_name: + * + * Returns: (transfer none): the name of the country. + */ +const gchar * +shell_country_mobile_provider_get_country_name (ShellCountryMobileProvider *country_provider) +{ + return country_provider->country_name; +} + +/** + * shell_country_mobile_provider_get_providers: + * + * Returns: (element-type Shell.MobileProvider) (transfer none): the + * list of #ShellMobileProvider this country exposes. + */ +GSList * +shell_country_mobile_provider_get_providers (ShellCountryMobileProvider *country_provider) +{ + return country_provider->providers; +} + +GType +shell_country_mobile_provider_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + type = g_boxed_type_register_static ("ShellCountryMobileProvider", + (GBoxedCopyFunc) shell_country_mobile_provider_ref, + (GBoxedFreeFunc) shell_country_mobile_provider_unref); + } + return type; +} + static void provider_list_free (gpointer data) { @@ -310,15 +395,17 @@ parser_toplevel_start (MobileParser *parser, for (i = 0; attribute_names && attribute_names[i]; i++) { if (!strcmp (attribute_names[i], "code")) { char *country_code; - char *country; + ShellCountryMobileProvider *country_provider; country_code = g_ascii_strup (attribute_values[i], -1); - country = g_hash_table_lookup (parser->country_codes, country_code); - if (country) { - parser->current_country = g_strdup (country); - g_free (country_code); - } else - parser->current_country = country_code; + country_provider = g_hash_table_lookup (parser->table, country_code); + /* Ensure we have a country provider for this country code */ + if (!country_provider) { + g_warning ("%s: adding providers for unknown country '%s'", __func__, country_code); + country_provider = country_mobile_provider_new (country_code, NULL); + g_hash_table_insert (parser->table, country_code, country_provider); + } + parser->current_country = country_code; parser->state = PARSER_COUNTRY; break; @@ -455,7 +542,13 @@ parser_country_end (MobileParser *parser, const char *name) { if (!strcmp (name, "country")) { - g_hash_table_insert (parser->table, parser->current_country, parser->current_providers); + ShellCountryMobileProvider *country_provider; + + country_provider = g_hash_table_lookup (parser->table, parser->current_country); + if (country_provider) + /* Store providers for this country */ + country_provider->providers = parser->current_providers; + parser->current_country = NULL; parser->current_providers = NULL; parser->text_buffer = NULL; @@ -616,15 +709,16 @@ static const GMarkupParser mobile_parser = { /** * shell_mobile_providers_parse: - * @out_ccs: (out) (allow-none): (element-type utf8 utf8): a #GHashTable containing - * country codes + * @country_codes: (allow-none) File with the list of country codes. + * @service_providers: (allow-none) File with the list of service providers. * - * Returns: (element-type utf8 GList) (transfer container): a - * hash table where keys are country names #gchar, values are a #GSList - * of #ShellMobileProvider. Everything is destroyed with g_hash_table_destroy(). -*/ + * Returns: (element-type utf8 Shell.CountryMobileProvider) (transfer full): a + * hash table where keys are country names #gchar and values are #ShellCountryMobileProvider. + * Everything is destroyed with g_hash_table_destroy(). + */ GHashTable * -shell_mobile_providers_parse (GHashTable **out_ccs) +shell_mobile_providers_parse (const gchar *country_codes, + const gchar *service_providers) { GMarkupParseContext *ctx; GIOChannel *channel; @@ -634,24 +728,29 @@ shell_mobile_providers_parse (GHashTable **out_ccs) GIOStatus status; gsize len = 0; + /* Use default paths if none given */ + if (!country_codes) + country_codes = ISO_3166_COUNTRY_CODES; + if (!service_providers) + service_providers = MOBILE_BROADBAND_PROVIDER_INFO; + memset (&parser, 0, sizeof (MobileParser)); - parser.country_codes = read_country_codes (); - if (!parser.country_codes) + parser.table = read_country_codes (country_codes); + if (!parser.table) goto out; - channel = g_io_channel_new_file (MOBILE_BROADBAND_PROVIDER_INFO, "r", &error); + channel = g_io_channel_new_file (service_providers, "r", &error); if (!channel) { if (error) { - g_warning ("Could not read " MOBILE_BROADBAND_PROVIDER_INFO ": %s", error->message); + g_warning ("Could not read %s: %s", service_providers, error->message); g_error_free (error); } else - g_warning ("Could not read " MOBILE_BROADBAND_PROVIDER_INFO ": Unknown error"); + g_warning ("Could not read %s: Unknown error", service_providers); goto out; } - parser.table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, provider_list_free); parser.state = PARSER_TOPLEVEL; ctx = g_markup_parse_context_new (&mobile_parser, 0, &parser, NULL); @@ -696,13 +795,7 @@ shell_mobile_providers_parse (GHashTable **out_ccs) g_free (parser.current_country); g_free (parser.text_buffer); - out: - if (parser.country_codes) { - if (out_ccs) - *out_ccs = parser.country_codes; - else - g_hash_table_destroy (parser.country_codes); - } +out: return parser.table; } @@ -744,12 +837,17 @@ dump_gsm (ShellMobileAccessMethod *method) static void dump_country (gpointer key, gpointer value, gpointer user_data) { - GSList *citer, *miter; + GSList *miter, *citer; + ShellCountryMobileProvider *country_provider = value; - for (citer = value; citer; citer = g_slist_next (citer)) { + g_print ("Country: %s (%s)\n", + country_provider->country_code, + country_provider->country_name); + + for (citer = country_provider->providers; citer; citer = g_slist_next (citer)) { ShellMobileProvider *provider = citer->data; - g_print ("Provider: %s (%s)\n", provider->name, (const char *) key); + g_print (" Provider: %s (%s)\n", provider->name, (const char *) key); for (miter = provider->methods; miter; miter = g_slist_next (miter)) { ShellMobileAccessMethod *method = miter->data; GSList *liter; @@ -779,10 +877,10 @@ dump_country (gpointer key, gpointer value, gpointer user_data) } void -shell_mobile_providers_dump (GHashTable *providers) +shell_mobile_providers_dump (GHashTable *country_providers) { - g_return_if_fail (providers != NULL); - g_hash_table_foreach (providers, dump_country, NULL); + g_return_if_fail (country_providers != NULL); + g_hash_table_foreach (country_providers, dump_country, NULL); } /* All the following don't exist in nm-applet, because C doesn't need diff --git a/src/shell-mobile-providers.h b/src/shell-mobile-providers.h index d70b8e2f0..4719601d9 100644 --- a/src/shell-mobile-providers.h +++ b/src/shell-mobile-providers.h @@ -76,10 +76,19 @@ typedef struct { gint refs; } ShellMobileProvider; +typedef struct { + char *country_code; + char *country_name; + GSList *providers; + + gint refs; +} ShellCountryMobileProvider; + GType shell_gsm_mcc_mnc_get_type (void); /* added in porting */ -GType shell_mobile_provider_get_type (void); GType shell_mobile_access_method_get_type (void); +GType shell_mobile_provider_get_type (void); +GType shell_country_mobile_provider_get_type (void); ShellMobileProvider *shell_mobile_provider_ref (ShellMobileProvider *provider); void shell_mobile_provider_unref (ShellMobileProvider *provider); @@ -89,8 +98,14 @@ GSList * shell_mobile_provider_get_cdma_sid (ShellMobileProvider *pr ShellMobileAccessMethod *shell_mobile_access_method_ref (ShellMobileAccessMethod *method); void shell_mobile_access_method_unref (ShellMobileAccessMethod *method); -GHashTable *shell_mobile_providers_parse (GHashTable **out_ccs); +ShellCountryMobileProvider *shell_country_mobile_provider_ref (ShellCountryMobileProvider *country_provider); +void shell_country_mobile_provider_unref (ShellCountryMobileProvider *country_provider); +const gchar *shell_country_mobile_provider_get_country_code (ShellCountryMobileProvider *country_provider); +const gchar *shell_country_mobile_provider_get_country_name (ShellCountryMobileProvider *country_provider); +GSList *shell_country_mobile_provider_get_providers (ShellCountryMobileProvider *country_provider); -void shell_mobile_providers_dump (GHashTable *providers); +GHashTable *shell_mobile_providers_parse (const gchar *country_codes, + const gchar *service_providers); +void shell_mobile_providers_dump (GHashTable *country_providers); #endif /* SHELL_MOBILE_PROVIDERS_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index e46463454..0704fd751 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -26,11 +26,14 @@ TEST_JS = \ testcommon/border-image.png \ testcommon/face-plain.png \ testcommon/ui.js \ + testcommon/iso3166-test.tab \ + testcommon/serviceproviders-test.xml \ unit/format.js \ unit/insertSorted.js \ unit/markup.js \ unit/jsParse.js \ - unit/url.js + unit/url.js \ + unit/mobileProviders.js EXTRA_DIST += $(TEST_JS) TEST_MISC = \ diff --git a/tests/testcommon/iso3166-test.tab b/tests/testcommon/iso3166-test.tab new file mode 100644 index 000000000..faa474e81 --- /dev/null +++ b/tests/testcommon/iso3166-test.tab @@ -0,0 +1,3 @@ +# Test country list +ES Spain +US United States \ No newline at end of file diff --git a/tests/testcommon/serviceproviders-test.xml b/tests/testcommon/serviceproviders-test.xml new file mode 100644 index 000000000..69240d773 --- /dev/null +++ b/tests/testcommon/serviceproviders-test.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + AT&T + + + + + + + + Verizon + + + + + + + + + + + + + + + + Movistar (Telefónica) + + + + + + + + diff --git a/tests/unit/mobileProviders.js b/tests/unit/mobileProviders.js new file mode 100644 index 000000000..4ae81317c --- /dev/null +++ b/tests/unit/mobileProviders.js @@ -0,0 +1,78 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const GLib = imports.gi.GLib; +const Shell = imports.gi.Shell; +const JsUnit = imports.jsUnit; +const ModemManager = imports.misc.modemManager; +const Environment = imports.ui.environment; + +Environment.init(); + +// Load test providers table +let countrycodesPath = GLib.getenv("GNOME_SHELL_TESTSDIR") + "/testcommon/iso3166-test.tab"; +let serviceprovidersPath = GLib.getenv("GNOME_SHELL_TESTSDIR") + "/testcommon/serviceproviders-test.xml"; +let providersTable = Shell.mobile_providers_parse(countrycodesPath, serviceprovidersPath); + +function assertCountryFound(country_code, expected_country_name) { + let country = providersTable[country_code]; + JsUnit.assertNotUndefined(country); + JsUnit.assertEquals(country.get_country_name(), expected_country_name); +} + +function assertCountryNotFound(country_code) { + let country = providersTable[country_code]; + JsUnit.assertUndefined(country); +} + +function assertProviderFoundForMCCMNC(mccmnc, expected_provider_name) { + let provider_name = ModemManager.findProviderForMCCMNC(providersTable, mccmnc); + JsUnit.assertEquals(provider_name, expected_provider_name); +} + +function assertProviderNotFoundForMCCMNC(mccmnc) { + let provider_name = ModemManager.findProviderForMCCMNC(providersTable, mccmnc); + JsUnit.assertNull(provider_name); +} + +function assertProviderFoundForSid(sid, expected_provider_name) { + let provider_name = ModemManager.findProviderForSid(providersTable, sid); + JsUnit.assertEquals(provider_name, expected_provider_name); +} + +function assertProviderNotFoundForSid(sid) { + let provider_name = ModemManager.findProviderForSid(providersTable, sid); + JsUnit.assertNull(provider_name); +} + +// TEST: +// * Both 'US' and 'ES' country info should be loaded +assertCountryFound("ES", "Spain"); +assertCountryFound("US", "United States"); + +// TEST: +// * Country info for 'FR' not given +assertCountryNotFound("FR"); + +// TEST: +// * Ensure operator names are found for the given MCC/MNC codes +assertProviderFoundForMCCMNC("21405", "Movistar (Telefónica)"); +assertProviderFoundForMCCMNC("21407", "Movistar (Telefónica)"); +assertProviderFoundForMCCMNC("310038", "AT&T"); +assertProviderFoundForMCCMNC("310090", "AT&T"); +assertProviderFoundForMCCMNC("310150", "AT&T"); +assertProviderFoundForMCCMNC("310995", "Verizon"); +assertProviderFoundForMCCMNC("311480", "Verizon"); + +// TEST: +// * Ensure NULL is given for unexpected MCC/MNC codes +assertProviderNotFoundForMCCMNC("12345"); + +// TEST: +// * Ensure operator names are found for the given SID codes +assertProviderFoundForSid(2, "Verizon"); +assertProviderFoundForSid(4, "Verizon"); +assertProviderFoundForSid(5, "Verizon"); + +// TEST: +// * Ensure NULL is given for unexpected SID codes +assertProviderNotFoundForSid(1);