/* * Copyright (C) 2017-2018 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #define AFB_BINDING_VERSION 3 #include #include "ofono_manager.h" #include "ofono_voicecallmanager.h" #include "ofono_voicecall.h" #define HFP_UUID "0000111f-0000-1000-8000-00805f9b34fb" static OrgOfonoVoiceCallManager *vcm; static OrgOfonoVoiceCall *incoming_call, *voice_call; static afb_event_t call_state_changed_event; static afb_event_t dialing_call_event; static afb_event_t incoming_call_event; static afb_event_t terminated_call_event; static void dial(afb_req_t request) { struct json_object *query, *val; const char *number; query = afb_req_json(request); json_object_object_get_ex(query, "value", &val); if (json_object_is_type(val, json_type_string)) { number = json_object_get_string(val); if (voice_call) { AFB_ERROR("dial: cannot dial with active call"); afb_req_fail(request, "active call", NULL); } else { AFB_DEBUG("dial: %s...\n", number); if (ofono_voicecallmanager_dial(vcm, (gchar *)number, "")) { afb_req_success(request, NULL, NULL); } else { AFB_ERROR("dial: failed to dial number\n"); afb_req_fail(request, "failed dial", NULL); } } } else { AFB_ERROR("dial: no phone number parameter\n"); afb_req_fail(request, "no number", NULL); } } static void hangup(afb_req_t request) { if (voice_call) { AFB_DEBUG("Hangup voice call\n"); ofono_voicecall_hangup(voice_call); afb_req_success(request, NULL, NULL); } else if (incoming_call) { AFB_DEBUG("Reject incoming call\n"); ofono_voicecall_hangup(incoming_call); afb_req_success(request, NULL, NULL); } else { AFB_ERROR("Hangup: no active call"); afb_req_fail(request, "failed hangup", NULL); } } static void answer(afb_req_t request) { if (incoming_call) { AFB_DEBUG("Answer voice call\n"); voice_call = incoming_call; ofono_voicecall_answer(voice_call); } else { AFB_ERROR("Answer: no incoming call"); } } static void subscribe(afb_req_t request) { const char *value = afb_req_value(request, "value"); if(value) { if (!strcasecmp(value, "callStateChanged")) { afb_req_subscribe(request, call_state_changed_event); } else if (!strcasecmp(value, "dialingCall")) { afb_req_subscribe(request, dialing_call_event); } else if (!strcasecmp(value, "incomingCall")) { afb_req_subscribe(request, incoming_call_event); } else if (!strcasecmp(value, "terminatedCall")) { afb_req_subscribe(request, terminated_call_event); } else { afb_req_fail(request, "failed", "Invalid event"); return; } } afb_req_success(request, NULL, NULL); } static void unsubscribe(afb_req_t request) { const char *value = afb_req_value(request, "value"); if(value) { if (!strcasecmp(value, "callStateChanged")) { afb_req_unsubscribe(request, call_state_changed_event); } else if (!strcasecmp(value, "dialingCall")) { afb_req_unsubscribe(request, dialing_call_event); } else if (!strcasecmp(value, "incomingCall")) { afb_req_unsubscribe(request, incoming_call_event); } else if (!strcasecmp(value, "terminatedCall")) { afb_req_unsubscribe(request, terminated_call_event); } else { afb_req_fail(request, "failed", "Invalid event"); return; } } afb_req_success(request, NULL, NULL); } static void call_state_changed_cb(OrgOfonoVoiceCall *vc, gchar *state) { struct json_object *call_state; call_state = json_object_new_object(); json_object_object_add(call_state, "state", json_object_new_string(state)); afb_event_push(call_state_changed_event, call_state); } static void incoming_call_cb(OrgOfonoVoiceCallManager *manager, gchar *op, gchar *clip) { struct json_object *call_info; call_info = json_object_new_object(); json_object_object_add(call_info, "clip", json_object_new_string(clip)); afb_event_push(incoming_call_event, call_info); incoming_call = ofono_voicecall_new(op, call_state_changed_cb); } static void dialing_call_cb(OrgOfonoVoiceCallManager *manager, gchar *op, gchar *colp) { struct json_object *call_info; call_info = json_object_new_object(); json_object_object_add(call_info, "colp", json_object_new_string(colp)); afb_event_push(dialing_call_event, call_info); voice_call = ofono_voicecall_new(op, call_state_changed_cb); } static void terminated_call_cb(OrgOfonoVoiceCallManager *manager, gchar *op) { if (incoming_call) { ofono_voicecall_free(incoming_call); incoming_call = NULL; } else if (voice_call) { ofono_voicecall_free(voice_call); } voice_call = NULL; afb_event_push(terminated_call_event, NULL); } static void *main_loop_thread(void *unused) { GMainLoop *loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(loop); return NULL; } static int ofono_init_default_modem(void) { int ret = 0; const gchar *modem_path = ofono_manager_get_default_modem_path(); if (modem_path) { vcm = ofono_voicecallmanager_init(modem_path, incoming_call_cb, dialing_call_cb, terminated_call_cb); if (!vcm) { AFB_ERROR("failed to initialize voice call manager\n"); ret = -1; } } else { AFB_ERROR("default modem not set\n"); ret = -1; } return ret; } static gboolean is_hfp_dev_and_init(struct json_object *dev) { struct json_object *props = NULL, *val = NULL; int i, connected = 0; json_object_object_get_ex(dev, "properties", &props); if (!props) return FALSE; json_object_object_get_ex(props, "connected", &val); connected = json_object_get_boolean(val); if (!val || !connected) return FALSE; json_object_object_get_ex(props, "uuids", &val); for (i = 0; i < json_object_array_length(val); i++) { const char *uuid = json_object_get_string(json_object_array_get_idx(val, i)); struct json_object *val1 = NULL; int ret; if (g_strcmp0(HFP_UUID, uuid)) continue; json_object_object_get_ex(props, "address", &val1); ret = ofono_manager_set_default_modem(json_object_get_string(val1)); if (ret != 0) return FALSE; ofono_init_default_modem(); return TRUE; } return FALSE; } static void discovery_result_cb(void *closure, struct json_object *result, const char *error, const char *info, afb_api_t api) { enum json_type type; struct json_object *dev, *tmp; int i; if (!json_object_object_get_ex(result, "devices", &tmp)) return; type = json_object_get_type(tmp); if (type != json_type_array) return; for (i = 0; i < json_object_array_length(tmp); i++) { dev = json_object_array_get_idx(tmp, i); if (is_hfp_dev_and_init(dev)) return; } } static void ofono_hfp_init(afb_api_t api) { struct json_object *args; args = json_object_new_object(); json_object_object_add(args, "value", json_object_new_string("device_changes")); afb_api_call_sync(api, "Bluetooth-Manager", "subscribe", args, NULL, NULL, NULL); args = json_object_new_object(); afb_api_call(api, "Bluetooth-Manager", "managed_objects", args, discovery_result_cb, NULL); } static int ofono_init(afb_api_t api) { pthread_t tid; int ret = 0; call_state_changed_event = afb_daemon_make_event("callStateChanged"); dialing_call_event = afb_daemon_make_event("dialingCall"); incoming_call_event = afb_daemon_make_event("incomingCall"); terminated_call_event = afb_daemon_make_event("terminatedCall"); ret = afb_daemon_require_api("Bluetooth-Manager", 1); if (ret) { AFB_ERROR("unable to initialize bluetooth binding"); return -1; } /* Start the main loop thread */ pthread_create(&tid, NULL, main_loop_thread, NULL); ret = ofono_manager_init(); if (ret == 0) { ofono_manager_invalidate_default_modem(); ofono_hfp_init(api); } else { AFB_ERROR("failed to initialize ofono manager"); } return ret; } static const afb_verb_t verbs[]= { { .verb = "dial", .callback = dial, }, { .verb = "hangup", .callback = hangup, }, { .verb = "answer", .callback = answer, }, { .verb = "subscribe", .callback = subscribe, }, { .verb = "unsubscribe", .callback = unsubscribe, }, { } }; static int init(afb_api_t api) { AFB_NOTICE("Initializing telephony service"); return ofono_init(api); } static void process_connection_event(afb_api_t api, struct json_object *object) { struct json_object *val = NULL, *props = NULL; const char *action, *address; int connected = 0; json_object_object_get_ex(object, "action", &val); if (!val) return; action = json_object_get_string(val); if (g_strcmp0("changed", action)) return; json_object_object_get_ex(object, "properties", &props); if (!props) return; json_object_object_get_ex(props, "connected", &val); if (!val) return; connected = json_object_get_boolean(val); if (connected) { struct json_object *args = json_object_new_object(); afb_api_call(api, "Bluetooth-Manager", "managed_objects", args, discovery_result_cb, NULL); return; } json_object_object_get_ex(props, "address", &val); if (!val) return; address = json_object_get_string(val); if (!g_strcmp0(address, ofono_manager_get_default_modem_address())) { ofono_manager_invalidate_default_modem(); ofono_voicecallmanager_free(vcm); } } static void onevent(afb_api_t api, const char *event, struct json_object *object) { if (!g_ascii_strcasecmp(event, "Bluetooth-Manager/device_changes")) process_connection_event(api, object); else AFB_ERROR("Unsupported event: %s\n", event); } const afb_binding_t afbBindingV3 = { .api = "telephony", .verbs = verbs, .init = init, .onevent = onevent, };