Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| /* | |
| * | |
| * oFono - Open Source Telephony | |
| * | |
| * Copyright (C) 2008-2011 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 | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <glib.h> | |
| #include <ell/ell.h> | |
| #include <ofono/log.h> | |
| #include <ofono/modem.h> | |
| #include <ofono/phonebook.h> | |
| #include "util.h" | |
| #include "gatchat.h" | |
| #include "gatresult.h" | |
| #include "atmodem.h" | |
| #include "vendor.h" | |
| #define INDEX_INVALID -1 | |
| #define CHARSET_UTF8 1 | |
| #define CHARSET_UCS2 2 | |
| #define CHARSET_IRA 4 | |
| #define CHARSET_SUPPORT (CHARSET_UTF8 | CHARSET_UCS2) | |
| static const char *none_prefix[] = { NULL }; | |
| static const char *cpbr_prefix[] = { "+CPBR:", NULL }; | |
| static const char *cscs_prefix[] = { "+CSCS:", NULL }; | |
| static const char *cpbs_prefix[] = { "+CPBS:", NULL }; | |
| struct pb_data { | |
| int index_min, index_max; | |
| char *old_charset; | |
| int supported; | |
| GAtChat *chat; | |
| unsigned int vendor; | |
| guint poll_source; | |
| guint poll_count; | |
| guint ready_id; | |
| }; | |
| static void warn_bad(void) | |
| { | |
| ofono_warn("Name field conversion to UTF8 failed, this can indicate a" | |
| " problem with modem integration, as this field" | |
| " is required by 27.007."); | |
| } | |
| static gboolean parse_text(GAtResultIter *iter, char **str, int encoding) | |
| { | |
| const char *string; | |
| const guint8 *hex; | |
| int len; | |
| char *utf8; | |
| /* charset_current is CHARSET_UCS2, CHARSET_IRA or CHARSET_UTF8 */ | |
| if (encoding == CHARSET_UCS2) { | |
| /* | |
| * Some devices omit the quotes, so use next_hexstring, | |
| * which handles quoted or unquoted hex strings | |
| */ | |
| if (g_at_result_iter_next_hexstring(iter, &hex, &len) == FALSE) | |
| return FALSE; | |
| utf8 = l_utf8_from_ucs2be(hex, len); | |
| if (!utf8) | |
| return FALSE; | |
| *str = utf8; | |
| return TRUE; | |
| } | |
| /* | |
| * In the case of IRA charset, assume these are Latin1 | |
| * characters, same as in UTF8 | |
| */ | |
| if (g_at_result_iter_next_string(iter, &string)) { | |
| *str = l_strdup(string); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| static const char *best_charset(int supported) | |
| { | |
| const char *charset = "Invalid"; | |
| if (supported & CHARSET_IRA) | |
| charset = "IRA"; | |
| if (supported & CHARSET_UCS2) | |
| charset = "UCS2"; | |
| if (supported & CHARSET_UTF8) | |
| charset = "UTF-8"; | |
| return charset; | |
| } | |
| static void at_cpbr_notify(GAtResult *result, gpointer user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| struct ofono_phonebook *pb = cbd->user; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| GAtResultIter iter; | |
| int current; | |
| if (pbd->supported & CHARSET_IRA) | |
| current = CHARSET_IRA; | |
| if (pbd->supported & CHARSET_UCS2) | |
| current = CHARSET_UCS2; | |
| if (pbd->supported & CHARSET_UTF8) | |
| current = CHARSET_UTF8; | |
| g_at_result_iter_init(&iter, result); | |
| while (g_at_result_iter_next(&iter, "+CPBR:")) { | |
| int index; | |
| const char *number; | |
| int type; | |
| char *text; | |
| int hidden = -1; | |
| char *group = NULL; | |
| const char *adnumber = NULL; | |
| int adtype = -1; | |
| char *secondtext = NULL; | |
| char *email = NULL; | |
| char *sip_uri = NULL; | |
| char *tel_uri = NULL; | |
| if (!g_at_result_iter_next_number(&iter, &index)) | |
| continue; | |
| if (!g_at_result_iter_next_string(&iter, &number)) | |
| continue; | |
| if (!g_at_result_iter_next_number(&iter, &type)) | |
| continue; | |
| if (!parse_text(&iter, &text, current)) { | |
| warn_bad(); | |
| continue; | |
| } | |
| g_at_result_iter_next_number_default(&iter, 0, &hidden); | |
| parse_text(&iter, &group, current); | |
| g_at_result_iter_next_string(&iter, &adnumber); | |
| g_at_result_iter_next_number_default(&iter, 0, &adtype); | |
| parse_text(&iter, &secondtext, current); | |
| parse_text(&iter, &email, current); | |
| parse_text(&iter, &sip_uri, current); | |
| parse_text(&iter, &tel_uri, current); | |
| ofono_phonebook_entry(pb, index, number, type, | |
| text, hidden, group, adnumber, | |
| adtype, secondtext, email, | |
| sip_uri, tel_uri); | |
| l_free(text); | |
| l_free(group); | |
| l_free(secondtext); | |
| l_free(email); | |
| l_free(sip_uri); | |
| l_free(tel_uri); | |
| } | |
| } | |
| static void export_failed(struct cb_data *cbd) | |
| { | |
| struct ofono_phonebook *pb = cbd->user; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| ofono_phonebook_cb_t cb = cbd->cb; | |
| CALLBACK_WITH_FAILURE(cb, cbd->data); | |
| g_free(cbd); | |
| if (pbd->old_charset) { | |
| g_free(pbd->old_charset); | |
| pbd->old_charset = NULL; | |
| } | |
| } | |
| static void at_read_entries_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| struct ofono_phonebook *pb = cbd->user; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| ofono_phonebook_cb_t cb = cbd->cb; | |
| const char *charset; | |
| struct ofono_error error; | |
| char buf[32]; | |
| decode_at_error(&error, g_at_result_final_response(result)); | |
| cb(&error, cbd->data); | |
| g_free(cbd); | |
| charset = best_charset(pbd->supported); | |
| if (strcmp(pbd->old_charset, charset)) { | |
| snprintf(buf, sizeof(buf), "AT+CSCS=\"%s\"", pbd->old_charset); | |
| g_at_chat_send(pbd->chat, buf, none_prefix, NULL, NULL, NULL); | |
| } | |
| g_free(pbd->old_charset); | |
| pbd->old_charset = NULL; | |
| } | |
| static void at_read_entries(struct cb_data *cbd) | |
| { | |
| struct ofono_phonebook *pb = cbd->user; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| char buf[32]; | |
| snprintf(buf, sizeof(buf), "AT+CPBR=%d,%d", | |
| pbd->index_min, pbd->index_max); | |
| if (g_at_chat_send_listing(pbd->chat, buf, cpbr_prefix, | |
| at_cpbr_notify, at_read_entries_cb, | |
| cbd, NULL) > 0) | |
| return; | |
| /* If we get here, then most likely connection to the modem dropped | |
| * and we can't really restore the charset anyway | |
| */ | |
| export_failed(cbd); | |
| } | |
| static void at_set_charset_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| if (!ok) { | |
| export_failed(cbd); | |
| return; | |
| } | |
| at_read_entries(cbd); | |
| } | |
| static void at_read_charset_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| struct ofono_phonebook *pb = cbd->user; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| GAtResultIter iter; | |
| const char *charset; | |
| char buf[32]; | |
| if (!ok) | |
| goto error; | |
| g_at_result_iter_init(&iter, result); | |
| if (!g_at_result_iter_next(&iter, "+CSCS:")) | |
| goto error; | |
| g_at_result_iter_next_string(&iter, &charset); | |
| pbd->old_charset = g_strdup(charset); | |
| charset = best_charset(pbd->supported); | |
| if (!strcmp(pbd->old_charset, charset)) { | |
| at_read_entries(cbd); | |
| return; | |
| } | |
| snprintf(buf, sizeof(buf), "AT+CSCS=\"%s\"", charset); | |
| if (g_at_chat_send(pbd->chat, buf, none_prefix, | |
| at_set_charset_cb, cbd, NULL) > 0) | |
| return; | |
| error: | |
| export_failed(cbd); | |
| } | |
| static void at_list_indices_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| struct ofono_phonebook *pb = cbd->user; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| GAtResultIter iter; | |
| if (!ok) | |
| goto error; | |
| g_at_result_iter_init(&iter, result); | |
| if (!g_at_result_iter_next(&iter, "+CPBR:")) | |
| goto error; | |
| if (!g_at_result_iter_open_list(&iter)) | |
| goto error; | |
| /* Retrieve index_min and index_max from indices | |
| * which seems like "(1-150),32,16" | |
| */ | |
| if (!g_at_result_iter_next_range(&iter, &pbd->index_min, | |
| &pbd->index_max)) | |
| goto error; | |
| if (!g_at_result_iter_close_list(&iter)) | |
| goto error; | |
| if (g_at_chat_send(pbd->chat, "AT+CSCS?", cscs_prefix, | |
| at_read_charset_cb, cbd, NULL) > 0) | |
| return; | |
| error: | |
| export_failed(cbd); | |
| } | |
| static void at_select_storage_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data) | |
| { | |
| struct cb_data *cbd = user_data; | |
| struct ofono_phonebook *pb = cbd->user; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| if (!ok) | |
| goto error; | |
| if (g_at_chat_send(pbd->chat, "AT+CPBR=?", cpbr_prefix, | |
| at_list_indices_cb, cbd, NULL) > 0) | |
| return; | |
| error: | |
| export_failed(cbd); | |
| } | |
| static void at_export_entries(struct ofono_phonebook *pb, const char *storage, | |
| ofono_phonebook_cb_t cb, void *data) | |
| { | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| struct cb_data *cbd = cb_data_new(cb, data); | |
| char buf[32]; | |
| cbd->user = pb; | |
| snprintf(buf, sizeof(buf), "AT+CPBS=\"%s\"", storage); | |
| if (g_at_chat_send(pbd->chat, buf, none_prefix, | |
| at_select_storage_cb, cbd, NULL) > 0) | |
| return; | |
| g_free(cbd); | |
| CALLBACK_WITH_FAILURE(cb, data); | |
| } | |
| static void phonebook_not_supported(struct ofono_phonebook *pb) | |
| { | |
| ofono_error("Phonebook not supported by this modem. If this is in " | |
| "error please submit patches to support this hardware"); | |
| ofono_phonebook_remove(pb); | |
| } | |
| static void at_list_storages_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data); | |
| static gboolean cpbs_support_check(gpointer user_data) | |
| { | |
| struct ofono_phonebook *pb = user_data; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| pbd->poll_source = 0; | |
| if (g_at_chat_send(pbd->chat, "AT+CPBS=?", cpbs_prefix, | |
| at_list_storages_cb, pb, NULL) > 0) | |
| return FALSE; | |
| phonebook_not_supported(pb); | |
| return FALSE; | |
| } | |
| static void ifx_pbready_notify(GAtResult *result, gpointer user_data) | |
| { | |
| struct ofono_phonebook *pb = user_data; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| g_at_chat_unregister(pbd->chat, pbd->ready_id); | |
| pbd->ready_id = 0; | |
| cpbs_support_check(pb); | |
| } | |
| static void at_list_storages_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data) | |
| { | |
| struct ofono_phonebook *pb = user_data; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| struct ofono_error error; | |
| gboolean sm_supported = FALSE; | |
| gboolean me_supported = FALSE; | |
| gboolean in_list = FALSE; | |
| GAtResultIter iter; | |
| const char *storage; | |
| decode_at_error(&error, g_at_result_final_response(result)); | |
| switch (error.type) { | |
| case OFONO_ERROR_TYPE_NO_ERROR: | |
| break; | |
| case OFONO_ERROR_TYPE_CME: | |
| /* Check for SIM busy - try again later */ | |
| if (error.error == 14) { | |
| if (pbd->poll_count++ < 12) { | |
| pbd->poll_source = g_timeout_add_seconds(5, | |
| cpbs_support_check, pb); | |
| return; | |
| } | |
| } | |
| /* fall through */ | |
| default: | |
| goto error; | |
| } | |
| g_at_result_iter_init(&iter, result); | |
| if (!g_at_result_iter_next(&iter, "+CPBS:")) | |
| goto error; | |
| /* Some modems don't report CPBS in a proper list */ | |
| if (g_at_result_iter_open_list(&iter)) | |
| in_list = TRUE; | |
| while (g_at_result_iter_next_string(&iter, &storage)) { | |
| if (!strcmp(storage, "ME")) | |
| me_supported = TRUE; | |
| else if (!strcmp(storage, "SM")) | |
| sm_supported = TRUE; | |
| } | |
| if (in_list && !g_at_result_iter_close_list(&iter)) | |
| goto vendor; | |
| if (!me_supported && !sm_supported) | |
| goto vendor; | |
| ofono_phonebook_register(pb); | |
| return; | |
| vendor: | |
| switch (pbd->vendor) { | |
| case OFONO_VENDOR_IFX: | |
| pbd->ready_id = g_at_chat_register(pbd->chat, "+PBREADY", | |
| ifx_pbready_notify, FALSE, pb, NULL); | |
| return; | |
| } | |
| error: | |
| phonebook_not_supported(pb); | |
| } | |
| static void at_list_charsets_cb(gboolean ok, GAtResult *result, | |
| gpointer user_data) | |
| { | |
| struct ofono_phonebook *pb = user_data; | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| gboolean in_list = FALSE; | |
| GAtResultIter iter; | |
| const char *charset; | |
| if (!ok) | |
| goto error; | |
| g_at_result_iter_init(&iter, result); | |
| if (!g_at_result_iter_next(&iter, "+CSCS:")) | |
| goto error; | |
| /* Some modems don't report CSCS in a proper list */ | |
| if (g_at_result_iter_open_list(&iter)) | |
| in_list = TRUE; | |
| while (g_at_result_iter_next_string(&iter, &charset)) { | |
| if (!strcmp(charset, "UTF-8")) | |
| pbd->supported |= CHARSET_UTF8; | |
| else if (!strcmp(charset, "UCS2")) | |
| pbd->supported |= CHARSET_UCS2; | |
| else if (!strcmp(charset, "IRA")) | |
| pbd->supported |= CHARSET_IRA; | |
| } | |
| if (in_list && !g_at_result_iter_close_list(&iter)) | |
| goto error; | |
| if (!(pbd->supported & CHARSET_SUPPORT)) { | |
| /* Some modems, like the Google G1, do not support UCS2 or UTF8 | |
| * Such modems are effectively junk, but we can still get some | |
| * useful information out of them by using IRA charset, which | |
| * is essentially Latin1. Still, all bets are off if a SIM | |
| * with UCS2 encoded entries is present. | |
| */ | |
| if (pbd->supported & CHARSET_IRA) { | |
| ofono_error("This modem does not support UCS2 or UTF8 " | |
| "character sets. This means no i18n " | |
| "phonebook is possible on this modem," | |
| " if this is in error, submit patches " | |
| "to properly support this hardware"); | |
| } else { | |
| goto error; | |
| } | |
| } | |
| pbd->poll_count = 0; | |
| cpbs_support_check(pb); | |
| return; | |
| error: | |
| phonebook_not_supported(pb); | |
| } | |
| static void at_list_charsets(struct ofono_phonebook *pb) | |
| { | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| if (g_at_chat_send(pbd->chat, "AT+CSCS=?", cscs_prefix, | |
| at_list_charsets_cb, pb, NULL) > 0) | |
| return; | |
| phonebook_not_supported(pb); | |
| } | |
| static int at_phonebook_probe(struct ofono_phonebook *pb, unsigned int vendor, | |
| void *data) | |
| { | |
| GAtChat *chat = data; | |
| struct pb_data *pbd; | |
| pbd = g_try_new0(struct pb_data, 1); | |
| if (pbd == NULL) | |
| return -ENOMEM; | |
| pbd->chat = g_at_chat_clone(chat); | |
| pbd->vendor = vendor; | |
| ofono_phonebook_set_data(pb, pbd); | |
| at_list_charsets(pb); | |
| return 0; | |
| } | |
| static void at_phonebook_remove(struct ofono_phonebook *pb) | |
| { | |
| struct pb_data *pbd = ofono_phonebook_get_data(pb); | |
| if (pbd->poll_source > 0) | |
| g_source_remove(pbd->poll_source); | |
| if (pbd->old_charset) | |
| g_free(pbd->old_charset); | |
| ofono_phonebook_set_data(pb, NULL); | |
| g_at_chat_unref(pbd->chat); | |
| g_free(pbd); | |
| } | |
| static const struct ofono_phonebook_driver driver = { | |
| .name = "atmodem", | |
| .probe = at_phonebook_probe, | |
| .remove = at_phonebook_remove, | |
| .export_entries = at_export_entries | |
| }; | |
| void at_phonebook_init(void) | |
| { | |
| ofono_phonebook_driver_register(&driver); | |
| } | |
| void at_phonebook_exit(void) | |
| { | |
| ofono_phonebook_driver_unregister(&driver); | |
| } |