/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <dbus/dbus.h>
#include "cras_telephony.h"
#include "cras_hfp_ag_profile.h"
#include "cras_hfp_slc.h"
#define CRAS_TELEPHONY_INTERFACE "org.chromium.cras.Telephony"
#define CRAS_TELEPHONY_OBJECT_PATH "/org/chromium/cras/telephony"
#define TELEPHONY_INTROSPECT_XML \
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
"<node>\n" \
" <interface name=\"" CRAS_TELEPHONY_INTERFACE "\">\n" \
" <method name=\"AnswerCall\">\n" \
" </method>\n" \
" <method name=\"IncomingCall\">\n" \
" <arg name=\"value\" type=\"s\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"TerminateCall\">\n" \
" </method>\n" \
" <method name=\"SetBatteryLevel\">\n" \
" <arg name=\"value\" type=\"i\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetSignalStrength\">\n" \
" <arg name=\"value\" type=\"i\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetServiceAvailability\">\n" \
" <arg name=\"value\" type=\"i\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetDialNumber\">\n" \
" <arg name=\"value\" type=\"s\" direction=\"in\"/>\n" \
" </method>\n" \
" <method name=\"SetCallheld\">\n" \
" <arg name=\"value\" type=\"i\" direction=\"in\"/>\n" \
" </method>\n" \
" </interface>\n" \
" <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" \
" <method name=\"Introspect\">\n" \
" <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" \
" </method>\n" \
" </interface>\n" \
"</node>\n"
static struct cras_telephony_handle telephony_handle;
/* Helper to extract a single argument from a DBus message. */
static DBusHandlerResult get_single_arg(DBusMessage *message,
int dbus_type, void *arg)
{
DBusError dbus_error;
dbus_error_init(&dbus_error);
if (!dbus_message_get_args(message, &dbus_error,
dbus_type, arg,
DBUS_TYPE_INVALID)) {
syslog(LOG_WARNING,
"Bad method received: %s",
dbus_error.message);
dbus_error_free(&dbus_error);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
return DBUS_HANDLER_RESULT_HANDLED;
}
/* Helper to send an empty reply. */
static void send_empty_reply(DBusConnection *conn, DBusMessage *message)
{
DBusMessage *reply;
dbus_uint32_t serial = 0;
reply = dbus_message_new_method_return(message);
if (!reply)
return;
dbus_connection_send(conn, reply, &serial);
dbus_message_unref(reply);
}
static DBusHandlerResult handle_incoming_call(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
struct hfp_slc_handle *handle;
DBusHandlerResult rc;
const char* number;
rc = get_single_arg(message, DBUS_TYPE_STRING, &number);
if (rc != DBUS_HANDLER_RESULT_HANDLED)
return rc;
handle = cras_hfp_ag_get_active_handle();
telephony_handle.callsetup = 1;
if (handle) {
hfp_event_update_callsetup(handle);
hfp_event_incoming_call(handle, number, 129);
}
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult handle_terminate_call(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
cras_telephony_event_terminate_call();
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult handle_answer_call(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
cras_telephony_event_answer_call();
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult handle_set_dial_number(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusHandlerResult rc;
const char *number;
rc = get_single_arg(message, DBUS_TYPE_STRING, &number);
if (rc != DBUS_HANDLER_RESULT_HANDLED)
return rc;
cras_telephony_store_dial_number(strlen(number), number);
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult handle_set_battery(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
struct hfp_slc_handle *handle;
DBusHandlerResult rc;
int value;
rc = get_single_arg(message, DBUS_TYPE_INT32, &value);
if (rc != DBUS_HANDLER_RESULT_HANDLED)
return rc;
handle = cras_hfp_ag_get_active_handle();
if (handle)
hfp_event_set_battery(handle, value);
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult handle_set_signal(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
struct hfp_slc_handle *handle;
DBusHandlerResult rc;
int value;
rc = get_single_arg(message, DBUS_TYPE_INT32, &value);
if (rc != DBUS_HANDLER_RESULT_HANDLED)
return rc;
handle = cras_hfp_ag_get_active_handle();
if (handle)
hfp_event_set_signal(handle, value);
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult handle_set_service(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
struct hfp_slc_handle *handle;
DBusHandlerResult rc;
int value;
rc = get_single_arg(message, DBUS_TYPE_INT32, &value);
if (rc != DBUS_HANDLER_RESULT_HANDLED)
return rc;
handle = cras_hfp_ag_get_active_handle();
if (handle)
hfp_event_set_service(handle, value);
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult handle_set_callheld(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
struct hfp_slc_handle *handle;
DBusHandlerResult rc;
int value;
rc = get_single_arg(message, DBUS_TYPE_INT32, &value);
if (rc != DBUS_HANDLER_RESULT_HANDLED)
return rc;
telephony_handle.callheld = value;
handle = cras_hfp_ag_get_active_handle();
if (handle)
hfp_event_update_callheld(handle);
send_empty_reply(conn, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
/* Handle incoming messages. */
static DBusHandlerResult handle_telephony_message(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
syslog(LOG_ERR, "Telephony message: %s %s %s",
dbus_message_get_path(message),
dbus_message_get_interface(message),
dbus_message_get_member(message));
if (dbus_message_is_method_call(message,
DBUS_INTERFACE_INTROSPECTABLE,
"Introspect")) {
DBusMessage *reply;
const char *xml = TELEPHONY_INTROSPECT_XML;
reply = dbus_message_new_method_return(message);
if (!reply)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
if (!dbus_message_append_args(reply,
DBUS_TYPE_STRING, &xml,
DBUS_TYPE_INVALID))
return DBUS_HANDLER_RESULT_NEED_MEMORY;
if (!dbus_connection_send(conn, reply, NULL))
return DBUS_HANDLER_RESULT_NEED_MEMORY;
dbus_message_unref(reply);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"IncomingCall")) {
return handle_incoming_call(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"TerminateCall")) {
return handle_terminate_call(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"AnswerCall")) {
return handle_answer_call(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"SetDialNumber")) {
return handle_set_dial_number(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"SetBatteryLevel")) {
return handle_set_battery(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"SetSignalStrength")) {
return handle_set_signal(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"SetServiceAvailability")) {
return handle_set_service(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_TELEPHONY_INTERFACE,
"SetCallheld")) {
return handle_set_callheld(conn, message, arg);
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/* Exported Interface */
void cras_telephony_start(DBusConnection *conn)
{
static const DBusObjectPathVTable control_vtable = {
.message_function = handle_telephony_message,
};
DBusError dbus_error;
telephony_handle.dbus_conn = conn;
dbus_connection_ref(telephony_handle.dbus_conn);
if (!dbus_connection_register_object_path(conn,
CRAS_TELEPHONY_OBJECT_PATH,
&control_vtable,
&dbus_error)) {
syslog(LOG_ERR,
"Couldn't register telephony control: %s: %s",
CRAS_TELEPHONY_OBJECT_PATH, dbus_error.message);
dbus_error_free(&dbus_error);
return;
}
}
void cras_telephony_stop()
{
if (!telephony_handle.dbus_conn)
return;
dbus_connection_unregister_object_path(telephony_handle.dbus_conn,
CRAS_TELEPHONY_OBJECT_PATH);
dbus_connection_unref(telephony_handle.dbus_conn);
telephony_handle.dbus_conn = NULL;
}
struct cras_telephony_handle* cras_telephony_get()
{
return &telephony_handle;
}
/* Procedure to answer a call from AG.
*
* HF(hands-free) AG(audio gateway)
* <-- Call answered
* <-- +CIEV: (call = 1)
* <-- +CIEV: (callsetup = 0)
*/
int cras_telephony_event_answer_call()
{
int rc;
struct hfp_slc_handle *handle;
handle = cras_hfp_ag_get_active_handle();
if (telephony_handle.call == 0) {
telephony_handle.call = 1;
if (handle) {
rc = hfp_event_update_call(handle);
if (rc)
return rc;
}
}
telephony_handle.callsetup = 0;
if (handle) {
rc = hfp_event_update_callsetup(handle);
if (rc)
return rc;
}
return 0;
}
/* Procedure to terminate a call from AG.
*
* HF(hands-free) AG(audio gateway)
* <-- Call dropped
* <-- +CIEV: (call = 0)
*/
int cras_telephony_event_terminate_call()
{
int rc;
struct hfp_slc_handle *handle;
handle = cras_hfp_ag_get_active_handle();
if (telephony_handle.call) {
telephony_handle.call = 0;
if (handle) {
rc = hfp_event_update_call(handle);
if (rc)
return rc;
}
}
if (telephony_handle.callsetup) {
telephony_handle.callsetup = 0;
if (handle) {
rc = hfp_event_update_callsetup(handle);
if (rc)
return rc;
}
}
return 0;
}
void cras_telephony_store_dial_number(int len,
const char *number)
{
if (telephony_handle.dial_number != NULL) {
free(telephony_handle.dial_number);
telephony_handle.dial_number = NULL;
}
if (len == 0)
return ;
telephony_handle.dial_number =
(char *) calloc(len + 1,
sizeof(*telephony_handle.dial_number));
strncpy(telephony_handle.dial_number, number, len);
syslog(LOG_ERR,
"store dial_number: \"%s\"", telephony_handle.dial_number);
}