diff options
Diffstat (limited to 'src/mm-shared-qmi.c')
-rw-r--r-- | src/mm-shared-qmi.c | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c index 84ec75d4..8bcfe55f 100644 --- a/src/mm-shared-qmi.c +++ b/src/mm-shared-qmi.c @@ -24,10 +24,1054 @@ #include <libqmi-glib.h> +#include "mm-log.h" #include "mm-iface-modem.h" +#include "mm-iface-modem-location.h" #include "mm-shared-qmi.h" /*****************************************************************************/ +/* Private data context */ + +#define PRIVATE_TAG "shared-qmi-private-tag" +static GQuark private_quark; + +typedef struct { + /* Location helpers */ + MMIfaceModemLocation *iface_modem_location_parent; + MMModemLocationSource enabled_sources; + QmiClient *pds_client; + gulong location_event_report_indication_id; +} Private; + +static void +private_free (Private *priv) +{ + if (priv->location_event_report_indication_id) + g_signal_handler_disconnect (priv->pds_client, priv->location_event_report_indication_id); + if (priv->pds_client) + g_object_unref (priv->pds_client); + g_slice_free (Private, priv); +} + +static Private * +get_private (MMSharedQmi *self) +{ + Private *priv; + + if (G_UNLIKELY (!private_quark)) + private_quark = g_quark_from_static_string (PRIVATE_TAG); + + priv = g_object_get_qdata (G_OBJECT (self), private_quark); + if (!priv) { + priv = g_slice_new0 (Private); + + /* Setup parent class' MMIfaceModemLocation */ + g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface); + priv->iface_modem_location_parent = MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface (self); + + g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); + } + + return priv; +} + +/*****************************************************************************/ +/* Location: Set SUPL server */ + +gboolean +mm_shared_qmi_location_set_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +set_agps_config_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsSetAgpsConfigOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_agps_config_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_set_agps_config_output_get_result (output, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + qmi_message_pds_set_agps_config_output_unref (output); +} + +static gboolean +parse_as_ip_port (const gchar *supl, + guint32 *out_ip, + guint32 *out_port) +{ + gboolean valid = FALSE; + gchar **split; + guint port; + guint32 ip; + + split = g_strsplit (supl, ":", -1); + if (g_strv_length (split) != 2) + goto out; + + if (!mm_get_uint_from_str (split[1], &port)) + goto out; + if (port == 0 || port > G_MAXUINT16) + goto out; + if (inet_pton (AF_INET, split[0], &ip) <= 0) + goto out; + + *out_ip = ip; + *out_port = port; + valid = TRUE; + +out: + g_strfreev (split); + return valid; +} + +static gboolean +parse_as_url (const gchar *supl, + GArray **out_url) +{ + gchar *utf16; + gsize utf16_len; + + utf16 = g_convert (supl, -1, "UTF-16BE", "UTF-8", NULL, &utf16_len, NULL); + *out_url = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), utf16_len), + utf16, + utf16_len); + g_free (utf16); + return TRUE; +} + +void +mm_shared_qmi_location_set_supl_server (MMIfaceModemLocation *self, + const gchar *supl, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiClient *client = NULL; + QmiMessagePdsSetAgpsConfigInput *input; + guint32 ip; + guint32 port; + GArray *url; + + if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, &client, + callback, user_data)) + return; + + input = qmi_message_pds_set_agps_config_input_new (); + + /* For multimode devices, prefer UMTS by default */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL); + else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL); + + if (parse_as_ip_port (supl, &ip, &port)) + qmi_message_pds_set_agps_config_input_set_location_server_address (input, ip, port, NULL); + else if (parse_as_url (supl, &url)) { + qmi_message_pds_set_agps_config_input_set_location_server_url (input, url, NULL); + g_array_unref (url); + } else + g_assert_not_reached (); + + qmi_client_pds_set_agps_config ( + QMI_CLIENT_PDS (client), + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)set_agps_config_ready, + g_task_new (self, NULL, callback, user_data)); + qmi_message_pds_set_agps_config_input_unref (input); +} + +/*****************************************************************************/ +/* Location: Load SUPL server */ + +gchar * +mm_shared_qmi_location_load_supl_server_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +get_agps_config_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsGetAgpsConfigOutput *output = NULL; + GError *error = NULL; + guint32 ip = 0; + guint32 port = 0; + GArray *url = NULL; + gchar *str = NULL; + + output = qmi_client_pds_get_agps_config_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + goto out; + } + + if (!qmi_message_pds_get_agps_config_output_get_result (output, &error)) + goto out; + + /* Prefer IP/PORT to URL */ + if (qmi_message_pds_get_agps_config_output_get_location_server_address ( + output, + &ip, + &port, + NULL) && + ip != 0 && + port != 0) { + struct in_addr a = { .s_addr = ip }; + gchar buf[INET_ADDRSTRLEN + 1]; + + memset (buf, 0, sizeof (buf)); + + if (!inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)) { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot convert numeric IP address (%u) to string", ip); + goto out; + } + + str = g_strdup_printf ("%s:%u", buf, port); + goto out; + } + + if (qmi_message_pds_get_agps_config_output_get_location_server_url ( + output, + &url, + NULL) && + url->len > 0) { + str = g_convert (url->data, url->len, "UTF-8", "UTF-16BE", NULL, NULL, NULL); + } + + if (!str) + str = g_strdup (""); + +out: + if (error) + g_task_return_error (task, error); + else { + g_assert (str); + g_task_return_pointer (task, str, g_free); + } + g_object_unref (task); + + if (output) + qmi_message_pds_get_agps_config_output_unref (output); +} + +void +mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiClient *client = NULL; + QmiMessagePdsGetAgpsConfigInput *input; + + if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, &client, + callback, user_data)) + return; + + input = qmi_message_pds_get_agps_config_input_new (); + + /* For multimode devices, prefer UMTS by default */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL); + else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL); + + qmi_client_pds_get_agps_config ( + QMI_CLIENT_PDS (client), + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)get_agps_config_ready, + g_task_new (self, NULL, callback, user_data)); + qmi_message_pds_get_agps_config_input_unref (input); +} + +/*****************************************************************************/ +/* Location: disable */ + +typedef struct { + QmiClientPds *client; + MMModemLocationSource source; +} DisableLocationGatheringContext; + +static void +disable_location_gathering_context_free (DisableLocationGatheringContext *ctx) +{ + if (ctx->client) + g_object_unref (ctx->client); + g_slice_free (DisableLocationGatheringContext, ctx); +} + +gboolean +mm_shared_qmi_disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +gps_service_state_stop_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + DisableLocationGatheringContext *ctx; + QmiMessagePdsSetGpsServiceStateOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_gps_service_state_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_set_gps_service_state_output_get_result (output, &error)) { + if (!g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + g_prefix_error (&error, "Couldn't set GPS service state: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_gps_service_state_output_unref (output); + return; + } + + g_error_free (error); + } + + qmi_message_pds_set_gps_service_state_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + g_assert (priv->location_event_report_indication_id != 0); + g_assert (priv->pds_client); + + g_signal_handler_disconnect (priv->pds_client, priv->location_event_report_indication_id); + priv->location_event_report_indication_id = 0; + g_clear_object (&priv->pds_client); + + mm_dbg ("GPS stopped"); + priv->enabled_sources &= ~ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +set_default_tracking_session_stop_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + DisableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_default_tracking_session_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_set_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_default_tracking_session_output_unref (output); + return; + } + + qmi_message_pds_set_default_tracking_session_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + /* Done */ + mm_dbg ("A-GPS disabled"); + priv->enabled_sources &= ~ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +get_default_tracking_session_stop_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + DisableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionInput *input; + QmiMessagePdsGetDefaultTrackingSessionOutput *output; + GError *error = NULL; + QmiPdsOperatingMode session_operation; + guint8 data_timeout; + guint32 interval; + guint32 accuracy_threshold; + + output = qmi_client_pds_get_default_tracking_session_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_get_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_get_default_tracking_session_output_unref (output); + return; + } + + qmi_message_pds_get_default_tracking_session_output_get_info ( + output, + &session_operation, + &data_timeout, + &interval, + &accuracy_threshold, + NULL); + + qmi_message_pds_get_default_tracking_session_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + if (session_operation == QMI_PDS_OPERATING_MODE_STANDALONE) { + /* Done */ + mm_dbg ("A-GPS already disabled"); + priv->enabled_sources &= ~ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + input = qmi_message_pds_set_default_tracking_session_input_new (); + qmi_message_pds_set_default_tracking_session_input_set_info ( + input, + QMI_PDS_OPERATING_MODE_STANDALONE, + data_timeout, + interval, + accuracy_threshold, + NULL); + qmi_client_pds_set_default_tracking_session ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)set_default_tracking_session_stop_ready, + task); + qmi_message_pds_set_default_tracking_session_input_unref (input); +} + +void +mm_shared_qmi_disable_location_gathering (MMIfaceModemLocation *_self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMSharedQmi *self; + Private *priv; + GTask *task; + DisableLocationGatheringContext *ctx; + + self = MM_SHARED_QMI (_self); + priv = get_private (self); + + task = g_task_new (self, NULL, callback, user_data); + + /* NOTE: no parent disable_location_gathering() implementation */ + + if (!(source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS))) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + if (!priv->pds_client) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "missing QMI PDS client"); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (DisableLocationGatheringContext); + ctx->source = source; + ctx->client = g_object_ref (priv->pds_client); + g_task_set_task_data (task, ctx, (GDestroyNotify)disable_location_gathering_context_free); + + /* Disable A-GPS? */ + if (source == MM_MODEM_LOCATION_SOURCE_AGPS) { + qmi_client_pds_get_default_tracking_session ( + ctx->client, + NULL, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)get_default_tracking_session_stop_ready, + task); + return; + } + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + MMModemLocationSource tmp; + + /* If no more GPS sources enabled, stop GPS */ + tmp = priv->enabled_sources; + tmp &= ~source; + if (!(tmp & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))) { + QmiMessagePdsSetGpsServiceStateInput *input; + + input = qmi_message_pds_set_gps_service_state_input_new (); + qmi_message_pds_set_gps_service_state_input_set_state (input, FALSE, NULL); + qmi_client_pds_set_gps_service_state ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_service_state_stop_ready, + task); + qmi_message_pds_set_gps_service_state_input_unref (input); + return; + } + + /* Otherwise, we have more GPS sources enabled, we shouldn't stop GPS, just + * return */ + priv->enabled_sources &= ~source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* The QMI implementation has a fixed set of capabilities supported. Arriving + * here means we tried to disable one which wasn't set as supported, which should + * not happen */ + g_assert_not_reached (); +} + +/*****************************************************************************/ +/* Location: enable */ + +static void +location_event_report_indication_cb (QmiClientPds *client, + QmiIndicationPdsEventReportOutput *output, + MMSharedQmi *self) +{ + QmiPdsPositionSessionStatus session_status; + const gchar *nmea; + + if (qmi_indication_pds_event_report_output_get_position_session_status ( + output, + &session_status, + NULL)) { + mm_dbg ("[GPS] session status changed: '%s'", + qmi_pds_position_session_status_get_string (session_status)); + } + + if (qmi_indication_pds_event_report_output_get_nmea_position ( + output, + &nmea, + NULL)) { + mm_dbg ("[NMEA] %s", nmea); + mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea); + } +} + +typedef struct { + QmiClientPds *client; + MMModemLocationSource source; +} EnableLocationGatheringContext; + +static void +enable_location_gathering_context_free (EnableLocationGatheringContext *ctx) +{ + if (ctx->client) + g_object_unref (ctx->client); + g_slice_free (EnableLocationGatheringContext, ctx); +} + +gboolean +mm_shared_qmi_enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +ser_location_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + EnableLocationGatheringContext *ctx; + QmiMessagePdsSetEventReportOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_event_report_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_set_event_report_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set event report: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_event_report_output_unref (output); + return; + } + + qmi_message_pds_set_event_report_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + mm_dbg ("Adding location event report indication handling"); + g_assert (!priv->pds_client); + g_assert (priv->location_event_report_indication_id == 0); + priv->pds_client = g_object_ref (client); + priv->location_event_report_indication_id = + g_signal_connect (priv->pds_client, + "event-report", + G_CALLBACK (location_event_report_indication_cb), + self); + + mm_dbg ("GPS started"); + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +auto_tracking_state_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsSetEventReportInput *input; + QmiMessagePdsSetAutoTrackingStateOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_pds_set_auto_tracking_state_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_set_auto_tracking_state_output_get_result (output, &error)) { + if (!g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + g_prefix_error (&error, "Couldn't set auto-tracking state: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_auto_tracking_state_output_unref (output); + return; + } + g_error_free (error); + } + + qmi_message_pds_set_auto_tracking_state_output_unref (output); + + /* Only gather standard NMEA traces */ + input = qmi_message_pds_set_event_report_input_new (); + qmi_message_pds_set_event_report_input_set_nmea_position_reporting (input, TRUE, NULL); + qmi_client_pds_set_event_report ( + client, + input, + 5, + NULL, + (GAsyncReadyCallback)ser_location_ready, + task); + qmi_message_pds_set_event_report_input_unref (input); +} + +static void +gps_service_state_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdsSetAutoTrackingStateInput *input; + QmiMessagePdsSetGpsServiceStateOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_gps_service_state_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_set_gps_service_state_output_get_result (output, &error)) { + if (!g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + g_prefix_error (&error, "Couldn't set GPS service state: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_gps_service_state_output_unref (output); + return; + } + g_error_free (error); + } + + qmi_message_pds_set_gps_service_state_output_unref (output); + + /* Enable auto-tracking for a continuous fix */ + input = qmi_message_pds_set_auto_tracking_state_input_new (); + qmi_message_pds_set_auto_tracking_state_input_set_state (input, TRUE, NULL); + qmi_client_pds_set_auto_tracking_state ( + client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)auto_tracking_state_start_ready, + task); + qmi_message_pds_set_auto_tracking_state_input_unref (input); +} + +static void +set_default_tracking_session_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + EnableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionOutput *output; + GError *error = NULL; + + output = qmi_client_pds_set_default_tracking_session_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_set_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_set_default_tracking_session_output_unref (output); + return; + } + + qmi_message_pds_set_default_tracking_session_output_unref (output); + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + /* Done */ + mm_dbg ("A-GPS enabled"); + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +get_default_tracking_session_start_ready (QmiClientPds *client, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self; + Private *priv; + EnableLocationGatheringContext *ctx; + QmiMessagePdsSetDefaultTrackingSessionInput *input; + QmiMessagePdsGetDefaultTrackingSessionOutput *output; + GError *error = NULL; + QmiPdsOperatingMode session_operation; + guint8 data_timeout; + guint32 interval; + guint32 accuracy_threshold; + + output = qmi_client_pds_get_default_tracking_session_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!qmi_message_pds_get_default_tracking_session_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get default tracking session: "); + g_task_return_error (task, error); + g_object_unref (task); + qmi_message_pds_get_default_tracking_session_output_unref (output); + return; + } + + self = g_task_get_source_object (task); + priv = get_private (self); + ctx = g_task_get_task_data (task); + + qmi_message_pds_get_default_tracking_session_output_get_info ( + output, + &session_operation, + &data_timeout, + &interval, + &accuracy_threshold, + NULL); + + qmi_message_pds_get_default_tracking_session_output_unref (output); + + if (session_operation == QMI_PDS_OPERATING_MODE_MS_ASSISTED) { + mm_dbg ("A-GPS already enabled"); + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + input = qmi_message_pds_set_default_tracking_session_input_new (); + qmi_message_pds_set_default_tracking_session_input_set_info ( + input, + QMI_PDS_OPERATING_MODE_MS_ASSISTED, + data_timeout, + interval, + accuracy_threshold, + NULL); + qmi_client_pds_set_default_tracking_session ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)set_default_tracking_session_start_ready, + task); + qmi_message_pds_set_default_tracking_session_input_unref (input); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *_self, + GAsyncResult *res, + GTask *task) +{ + MMSharedQmi *self = MM_SHARED_QMI (_self); + Private *priv; + EnableLocationGatheringContext *ctx; + GError *error = NULL; + QmiClient *client; + + priv = get_private (self); + + if (!priv->iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx = g_task_get_task_data (task); + + /* We only consider GPS related sources in this shared QMI implementation */ + if (!(ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS))) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Setup context and client */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDS, + MM_PORT_QMI_FLAG_DEFAULT, + &error); + if (!client) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + ctx->client = g_object_ref (client); + + /* Enabling A-GPS? */ + if (ctx->source == MM_MODEM_LOCATION_SOURCE_AGPS) { + qmi_client_pds_get_default_tracking_session ( + ctx->client, + NULL, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)get_default_tracking_session_start_ready, + task); + return; + } + + /* NMEA and RAW are both enabled in the same way */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Only start GPS engine if not done already */ + if (!(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))) { + QmiMessagePdsSetGpsServiceStateInput *input; + + input = qmi_message_pds_set_gps_service_state_input_new (); + qmi_message_pds_set_gps_service_state_input_set_state (input, TRUE, NULL); + qmi_client_pds_set_gps_service_state ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_service_state_start_ready, + task); + qmi_message_pds_set_gps_service_state_input_unref (input); + return; + } + + /* GPS already started, we're done */ + priv->enabled_sources |= ctx->source; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* The shared QMI implementation has a fixed set of capabilities supported. Arriving + * here means we tried to enable one which wasn't set as supported, which should + * not happen */ + g_assert_not_reached (); +} + +void +mm_shared_qmi_enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnableLocationGatheringContext *ctx; + GTask *task; + Private *priv; + + ctx = g_slice_new0 (EnableLocationGatheringContext); + ctx->source = source; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)enable_location_gathering_context_free); + + priv = get_private (MM_SHARED_QMI (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->enable_location_gathering); + g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish); + + /* Chain up parent's gathering enable */ + priv->iface_modem_location_parent->enable_location_gathering ( + self, + ctx->source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + task); +} + +/*****************************************************************************/ +/* Location: load capabilities */ + +MMModemLocationSource +mm_shared_qmi_location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + GError *inner_error = NULL; + gssize value; + + value = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_MODEM_LOCATION_SOURCE_NONE; + } + return (MMModemLocationSource)value; +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GTask *task) +{ + MMModemLocationSource sources; + GError *error = NULL; + Private *priv; + + priv = get_private (MM_SHARED_QMI (self)); + + sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Now our own checks */ + + /* If we have support for the PDS client, GPS and A-GPS location is supported */ + if (mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDS, MM_PORT_QMI_FLAG_DEFAULT, NULL)) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW | + MM_MODEM_LOCATION_SOURCE_AGPS); + + /* So we're done, complete */ + g_task_return_int (task, sources); + g_object_unref (task); +} + +void +mm_shared_qmi_location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + Private *priv; + + task = g_task_new (self, NULL, callback, user_data); + + priv = get_private (MM_SHARED_QMI (self)); + g_assert (priv->iface_modem_location_parent); + g_assert (priv->iface_modem_location_parent->load_capabilities); + g_assert (priv->iface_modem_location_parent->load_capabilities_finish); + + priv->iface_modem_location_parent->load_capabilities (self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + task); +} + +/*****************************************************************************/ QmiClient * mm_shared_qmi_peek_client (MMSharedQmi *self, @@ -78,6 +1122,7 @@ mm_shared_qmi_get_type (void) shared_qmi_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedQmi", &info, 0); g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM); + g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM_LOCATION); } return shared_qmi_type; |