Implements the latest W3C Telephony API specification found at http://telephony.sysapps.org/
Requires the oFono telephony middleware and the BlueZ Bluetooth middleware for working with paired phones.
BUG=XWALK-1419
--- /dev/null
+<!--
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+-->
+
+<html>
+<style>
+body {
+ color:black;
+ font-family:verdana;
+ font-size:80%
+}
+h1 {
+ color:blue;
+ font-family:verdana;
+ font-size:150%;
+}
+p {
+ color:black;
+ font-family:verdana;
+ font-size:80%
+}
+</style>
+<h1>Test W3C Telephony API</h1>
+<body>
+ <button id="clearconsole_btn" onclick="clearConsole()">Clear Console Output</button>
+ <button id="add_service_listener_btn" onclick="addServiceListeners()" >
+ Add service listeners</button>
+ <button id="add_call_listeners_btn" onclick="addCallListeners()" >
+ Add call listeners</button>
+ <button id="services_btn" onclick="getServiceIds()" >Get services</button>
+ <button id="get_dservice_btn" onclick="getDefaultService()">
+ Get default service</button>
+ <br>
+ <button id="active_call_btn" onclick="getActiveCall()" >
+ Get active call</button>
+ <button id="get_calls_btn" onclick="getCalls()" >Get all calls </button>
+ <button id="dial_btn" onclick="dial()" >Dial</button>
+ number: <input type="text" size=20 id="dial_input">
+ <br>
+ <button id="conf_btn" onclick="createConference()" >
+ Create conference</button>
+ <button id="conf_parties_btn" onclick="getParticipants()" >
+ Get conference participants</button>
+ <button id="split_btn" onclick="split()">Split</button>
+ call id: <input type="text" size=25 id="split_input">
+ <br>
+ <button id="disconnect_btn" onclick="disconnect()">
+ Disconnect active call</button>
+ <button id="disconnect_all_btn" onclick="hangupAllCalls()">
+ Disconnect all calls</button>
+ <button id="hold_btn" onclick="hold()">Hold active call</button>
+ <button id="resume_btn" onclick="resume()">Resume held call</button>
+ <br>
+ <button id="accept_btn" onclick="accept()">
+ Accept incoming/waiting call</button>
+ <button id="deflect_btn" onclick="deflect()">Deflect incoming/waiting call</button>
+ to number: <input type="text" size=25 id="deflect_input">
+ <br>
+ <button id="transfer_btn" onclick="transfer()">Transfer incoming/waiting call </button>
+ <button id="emerg_nr_btn" onclick="getEmergencyNumbers()">
+ Get emergency numbers</button>
+ <button id="remove_call_listeners_btn" onclick="removeCallListeners()" >
+ Remove call listeners</button>
+ <button id="remove_service_listeners_btn" onclick="removeServiceListeners()" >
+ Remove service listeners</button>
+ <br>
+ <button id="sendtones_btn" onclick="sendTones()">Send Tones</button>
+ [0..9, A..F, p]: <input type="text" size=15 id="tones_input">
+ <button id="starttone_btn" onclick="startTone()">Start Tone</button>
+ <button id="endtone_btn" onclick="endTone()">End Tone</button>
+ <br>
+ Service id: <input type="text" size=30 id="service_id_input">
+ <button id="get_service_btn" onclick="getService()">
+ Show</button>
+ <button id="set_dservice_btn" onclick="setDefaultService()">
+ Set as default</button>
+ <button id="enable_service_btn" onclick="enableService()">
+ Enable</button>
+ <button id="disable_service_btn" onclick="disableService()">
+ Disable</button>
+ <br>
+ <textarea cols=90 rows=22 id="consolelog"></textarea>
+ <br>
+</body>
+
+<script>
+ var output = document.getElementById("consolelog");
+
+ function clearConsole() {
+ output.value = '';
+ }
+
+ function onException(error, text) { // there should be no exceptions raised
+ var t = text === undefined ? '' : '[' + text + ']';
+ var en = error === undefined ? "" : ':'+ error.name;
+ var em = error === undefined ? "" : ';' + error.message;
+ print('Exception' + t + en + em);
+ return null;
+ }
+
+ function onError(error, text) {
+ var t, en, em;
+ if (error) {
+ en = ':' + error.name;
+ em = ';' + error.message;
+ } else {
+ em = en = '';
+ }
+ if (text)
+ t = '[' + text + ']';
+ else
+ t = null;
+ print('Error' + t + en + em);
+ return null;
+ }
+
+ function printVal(thing, depth) {
+ var out, key;
+ if (thing instanceof Function) {
+ out += '{}';
+ } else if (thing instanceof Array) {
+ out = '[ ';
+ for (key in thing)
+ out += printVal(thing[key], depth + 1) + ', ';
+ out += ']';
+ } else if (thing instanceof Object) {
+ var tabs = '';
+ for (var i = 0; i < depth; i++)
+ tabs += '\t';
+ out = '\n' + tabs + '{';
+ for (key in thing) {
+ if (!(thing[key] instanceof Function))
+ out += '\n' + tabs + '\t' + key + ': ' +
+ printVal(thing[key], depth + 1);
+ }
+ out += '\n' + tabs + '}';
+ } else {
+ out = thing;
+ }
+ return out;
+ }
+
+ function print(o) {
+ output.value += '\n' + printVal(o, 0);
+ output.scrollTop = output.scrollHeight;
+ }
+
+ function displayEntryList(array) {
+ output.value += '\nList count = ' + array.length + '; ' +
+ printVal(array, 0);
+ }
+
+ function readStringField(name) {
+ var input = document.getElementById(name);
+ if (input)
+ return input.value || null;
+ return onError(null, "Invalid " + name + ": " + id);
+ }
+</script>
+
+<!--
+///////////////////////////////////////////////////////////////////////////////
+// simulation of tizen environment on desktop
+- ->
+<script>
+var extension = function() {};
+extension.messageListeners = [];
+extension.message = "";
+
+extension.postMessage = function(msg) {
+ print("extension.postMessage(" + msg + ")");
+}
+
+extension.setMessageListener = function(fun) {
+ print("extension.setMessageListener(" + fun.toString() + ")");
+ extension.messageListeners.push(fun);
+}
+
+var exports = new Object();
+var tizen = new Object();
+</script>
+
+<script type="text/javascript" src="../tizen/tizen_api.js">
+</script>
+
+<script>
+ for(key in exports) {
+ if (!tizen[key])
+ Object.defineProperty(tizen, key, {
+ configurable: false,
+ writable: false,
+ value: exports[key]
+ });
+ }
+ exports = new Object();
+</script>
+
+<script type="text/javascript" src="telephony_api.js">
+</script>
+
+<script type="text/javascript">
+tizen.telephony = exports;
+</script>
+<!- -
+///////////////////////////////////////////////////////////////////////////////
+// end of simulation of tizen environment
+-->
+
+<script>
+ var telephony = tizen.telephony;
+
+ function getServiceIds() {
+ print("Getting telephony service id's...");
+ tizen.telephony.getServiceIds().then(
+ function(list) {
+ displayEntryList(list);
+ },
+ function(err) {
+ onError(err, 'getServiceIds');
+ });
+ }
+
+ function readService() {
+ var id = readStringField('service_id_input');
+ var s = tizen.telephony.getService(id);
+ if (!s)
+ return onError(null, "No service for id: " + id);
+ return s;
+ }
+
+ function setDefaultService() {
+ var id = readStringField('service_id_input');
+ tizen.telephony.setDefaultServiceId(id).then(
+ function() {
+ print('Default telephony service id set to ' + id);
+ },
+ function(err) {
+ onError(err, 'setDefaultService');
+ });
+ }
+
+ function getDefaultService() {
+ print("Default service id: " + tizen.telephony.defaultServiceId);
+ }
+
+ function getService() {
+ var service = readService();
+ if (service)
+ print('service[' + id + ']: ' + printVal(service));
+ }
+
+ function enableService() {
+ var service = readService();
+ if (service) {
+ service.setEnabled(true).then(
+ function() {
+ print('Enabled service id ' + service.serviceId);
+ },
+ function(err) {
+ onError(err, 'enableService');
+ });
+ }
+ }
+
+ function disableService() {
+ var service = readService();
+ if (service) {
+ service.setEnabled(false).then(
+ function() {
+ print('Disabled service id ' + service.serviceId);
+ },
+ function(err) {
+ onError(err, 'disableService');
+ });
+ }
+ }
+
+ function onServiceAdded(evt) {
+ print("onServiceAdded: " + printVal(evt.service));
+ }
+
+ function onServiceRemoved(evt) {
+ print("onServiceRemoved: " + printVal(evt.service));
+ }
+
+ function onDefaultServiceChanged(evt) {
+ print("onDefaultServiceChanged: " + printVal(evt.service));
+ }
+
+ function onCallAdded(evt) {
+ print("onCallAdded: " + printVal(evt.call));
+ }
+
+ function onCallRemoved(evt) {
+ print("onCallRemoved: " + printVal(evt.call));
+ }
+
+ function onActiveCallChanged(evt) {
+ print("onActiveCallChanged to: " + evt.call ? evt.call.callId : "null");
+ }
+
+ function onCallStateChanged(evt) {
+ print("onCallStateChanged: " + printVal(evt.call));
+ }
+
+ function addServiceListeners() {
+ if (!telephony) {
+ return onError(null, "telephony not supported");
+ }
+ tizen.telephony.addEventListener('serviceadded', onServiceAdded, false);
+ tizen.telephony.addEventListener('serviceremoved', onServiceRemoved, false);
+ tizen.telephony.addEventListener('defaultservicechanged', onDefaultServiceChanged, false);
+ print("Event listeners added for 'serviceadded', 'serviceremoved', 'defaultservicechanged'.");
+ }
+
+ function removeServiceListeners() {
+ if (!telephony) {
+ return onError(null, "telephony not supported");
+ }
+ tizen.telephony.removeEventListener('serviceadded', onServiceAdded, false);
+ tizen.telephony.removeEventListener('serviceremoved', onServiceRemoved, false);
+ tizen.telephony.removeEventListener('defaultservicechanged', onDefaultServiceChanged, false);
+ print("Event listeners removed for 'serviceadded', 'serviceremoved', 'defaultservicechanged'.");
+ }
+
+ function addCallListeners() {
+ if (!telephony) {
+ return onError(null, "telephony not supported");
+ }
+ tizen.telephony.addEventListener('calladded', onCallAdded, false);
+ tizen.telephony.addEventListener('callremoved', onCallRemoved, false);
+ tizen.telephony.addEventListener('activecallchanged', onActiveCallChanged, false);
+ tizen.telephony.addEventListener('callstatechanged', onCallStateChanged, false);
+ print("Event listeners added for 'calladded', 'callremoved', 'activecallchanged', 'callstatechanged'.");
+ }
+
+ function removeCallListeners() {
+ if (!telephony) {
+ return onError(null, "telephony not supported");
+ }
+ tizen.telephony.removeEventListener('calladded', onCallAdded, false);
+ tizen.telephony.removeEventListener('callremoved', onCallRemoved, false);
+ tizen.telephony.removeEventListener('activecallchanged', onActiveCallChanged, false);
+ tizen.telephony.removeEventListener('callstatechanged', onCallStateChanged, false);
+ print("Event listeners added for 'calladded', 'callremoved', 'activecallchanged', 'callstatechanged'.");
+ }
+
+ function getActiveCall() {
+ var ac = tizen.telephony.activeCall;
+ if (!ac)
+ print("No active call");
+ else
+ print("Active call: " + printVal(ac));
+ }
+
+ function getCalls() {
+ print('Getting telephony calls...');
+ tizen.telephony.getCalls().then(
+ function(list) {
+ displayEntryList(list);
+ },
+ function(err) {
+ onError(err, 'getCalls');
+ });
+ }
+
+ function hangupAllCalls() {
+ print("Disconnecting all calls...");
+ tizen.telephony.getCalls().then(
+ function(list) {
+ var found = false;
+ list.forEach(function(call) {
+ if (call.state == 'held' || call.state == 'active') {
+ found = true;
+ call.disconnect().then(
+ function(){
+ print("Call " + call.callId + " disconnected.");
+ },
+ function(err) {
+ onError(err, 'disconnect');
+ });
+ }
+ });
+ if (!found)
+ print('No calls.');
+ },
+ function(err) {
+ onError(err, 'getCalls');
+ });
+ }
+
+ function createConference() {
+ print("Creating conference call...");
+ if (!tizen.telephony.activeCall) {
+ print("No active call.");
+ return;
+ }
+ tizen.telephony.createConference().then(
+ function(confCall) {
+ print('Conference call created: ' + printVal(confCall));
+ },
+ function(err) {
+ onError(err, 'createConference');
+ });
+ }
+
+ function getParticipants() {
+ print("Getting participants of active conference call...");
+ var ac = tizen.telephony.activeCall;
+ if (!ac) {
+ print("No active call.");
+ } else if (!ac.conferenceId) {
+ print("Active call not a conference. Remote party: " + ac.remoteParty);
+ return;
+ }
+ tizen.telephony.getParticipants(id).then(
+ function(list) {
+ print('Conference participant calls: ');
+ displayEntryList(list);
+ },
+ function(err) {
+ onError(err, 'getParticipants');
+ });
+ }
+
+ function split() {
+ var id = readStringField('split_input');
+ print("Splitting call id " + id + ' from its conference call');
+ tizen.telephony.split(id).then(
+ function() {
+ print('Call id ' + id + ' split from conference and activated');
+ },
+ function(err) {
+ onError(err, 'split');
+ });
+ }
+
+ function dial() {
+ var number = readStringField('dial_input');
+ if (number) {
+ print('Dialing ' + number);
+ tizen.telephony.dial(number).then(
+ function() {
+ print('Dialing ' + number + ' successful.');
+ },
+ function(err) {
+ onError(err, 'dial');
+ });
+ }
+ }
+
+ function accept() {
+ print("Accepting incoming/waiting call...");
+ tizen.telephony.getCalls().then(
+ function(list) {
+ var found = false;
+ list.forEach(function(call) {
+ if (call.state == 'incoming' || call.state == 'waiting') {
+ var state = call.state;
+ found = true;
+ call.accept().then(
+ function(){
+ print("Accepted " + state + " call: ");
+ print(call);
+ },
+ function(err) {
+ onError(err, 'accept');
+ });
+ }
+ });
+ if (!found)
+ print("No incoming or waiting calls")
+ },
+ function(err) {
+ onError(err, 'getCalls');
+ });
+ }
+
+ function disconnect() {
+ print("Disconnecting active call...");
+ if (!tizen.telephony.activeCall) {
+ print("No active calls");
+ return;
+ }
+ var id = tizen.telephony.activeCall.callId;
+ tizen.telephony.activeCall.disconnect().then(
+ function() {
+ print('Disconnected call id ' + id);
+ },
+ function(err) {
+ onError(err, 'disconnect');
+ });
+ }
+
+ function hold() {
+ print("Holding active call...");
+ if (!tizen.telephony.activeCall) {
+ print("No active calls");
+ return;
+ }
+ var call = tizen.telephony.activeCall;
+ call.hold().then(
+ function() {
+ print('Held call id ' + call.callId);
+ },
+ function(err) {
+ onError(err, 'hold');
+ });
+ }
+
+ function resume() {
+ print("Resuming held call...");
+ tizen.telephony.getCalls().then(
+ function(list) {
+ var found = false;
+ list.forEach(function(call) {
+ if (call.state == 'held') {
+ found = true;
+ call.resume().then(
+ function(){
+ print("Resumed call: " + call.callId);
+ },
+ function(err) {
+ onError(err, 'resume');
+ });
+ }
+ });
+ if (!found)
+ print("No held calls")
+ },
+ function(err) {
+ onError(err, 'getCalls');
+ });
+ }
+
+ function deflect() {
+ print("Deflecting incoming/waiting call...");
+ var number = readStringField('deflect_input');
+ if (!number)
+ return;
+ tizen.telephony.getCalls().then(
+ function(list) {
+ var found = false;
+ list.forEach(function(call) {
+ if (call.state == 'incoming' || call.state == 'waiting') {
+ var state = call.state;
+ found = true;
+ call.deflect(number).then(
+ function(){
+ print("Deflected " + state + " call: " + call.callId);
+ },
+ function(err) {
+ onError(err, 'deflect');
+ });
+ }
+ });
+ if (!found)
+ print("No incoming or waiting calls")
+ },
+ function(err) {
+ onError(err, 'getCalls');
+ });
+ }
+
+ function transfer() {
+ print("Transfer: joining the active and held calls, then disconnect...");
+ if (!tizen.telephony.activeCall) {
+ print("No active call");
+ return;
+ }
+ // not checking the held calls now, the system will signal error anyway
+ tizen.telephony.transfer().then(
+ function(){
+ print("Transferred " + state + " call: " + call.callId);
+ },
+ function(err) {
+ onError(err, 'transfer');
+ });
+ }
+
+ function sendTones() {
+ var tones = readStringField('tones_input');
+ if (!tones)
+ return;
+ tizen.telephony.sendTones(tones).then(
+ function() {
+ print('Tones sent: ' + tones);
+ },
+ function(err) {
+ onError(err, 'sendTones');
+ });
+ }
+
+ function startTone() {
+ var tones = readStringField('tones_input');
+ if (!tones)
+ return;
+ tizen.telephony.startTone(tones).then(
+ function() {
+ print('Tone started: ' + tones);
+ },
+ function(err) {
+ onError(err, 'startTone');
+ });
+ }
+
+ function stopTone() {
+ var tones = readStringField('tones_input');
+ if (!tones)
+ return;
+ tizen.telephony.stopTone(tones).then(
+ function() {
+ print('Tone stopped: ' + tones);
+ },
+ function(err) {
+ onError(err, 'stopTone');
+ });
+ }
+
+ function getEmergencyNumbers() {
+ tizen.telephony.getEmergencyNumbers().then(
+ function(list) {
+ print('Emergency number list: ');
+ displayEntryList(list);
+ },
+ function(err) {
+ onError(err, 'getEmergencyNumbers');
+ });
+ }
+
+</script>
+</html>
--- /dev/null
+## Introduction
+This is the implementation of the W3C Telephony API for the Crosswalk runtime,
+as an external Crosswalk extension.
+
+## Specification
+The API specification is located at http://telephony.sysapps.org
+
+## Back-end
+The back-end on Tizen IVI is oFono. Check documentation at
+http://git.kernel.org/cgit/network/ofono/ofono.git/tree/doc/
+and source code at
+http://git.kernel.org/cgit/network/ofono/ofono.git/.
+
+## Testing
+On the test page, first add service and call listeners.
+Then get services, and get default service.
+Then make a call, check active call, list calls, hold/resume/disconnect etc.
+
+Hints for testing on a device supporting either built-in telephony modems
+and/or Bluetooth HFP.
+
+Install packages needed for testing:
+zypper in bluez-test ofono-test tizen-extensions-crosswalk-examples
+
+Also, install helpers:
+zypper in glibc-devel glibc-devel-utils linux-glibc-devel gdb findutils-locate
+
+Unblock the rfkill on Bluetooth:
+rfkill list
+Identify the number for Bluetooth and unblock
+rfkill unblock 0
+
+Enable xwalk verbose logging, as root:
+vi /usr/lib/systemd/user/xwalk.service
+Modify xwalk.service to include "--enable-logging --v=1"
+
+Enable core dumps, as root:
+chsmack -e System /lib/systemd/systemd-coredump
+
+If want to coredump into files instead of systemd-coredumpctl, as root:
+sysctl kernel.core_pattern=core
+sysctl kernel.core_uses_pid=1
+
+Set limits for core, as app:
+ulimit -c unlimited
+
+Set up environment (and proxy) as app:
+export XDG_RUNTIME_DIR=/run/user/`id -u`
+
+systemctl --user daemon-reload
+
+Pair and connect a phone, as app:
+bluetoothctl
+ list
+ agent on
+ discoverable on
+ pairable on
+ scan on
+ pair <device>
+ connect <device>
+ quit
+
+To stop crosswalk, as app:
+systemctl --user stop xwalk
+
+To launch the test page:
+xwalk-launcher file:///home/app/telephony_test.html
+
+To debug, attach gdb to the xwalk-launcher process (it becomes the extension
+process for the app).
--- /dev/null
+{
+ 'includes':[
+ '../common/common.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'tizen_telephony',
+ 'type': 'loadable_module',
+ 'variables': {
+ 'packages': [
+ 'gio-2.0',
+ ],
+ },
+ 'includes': [
+ '../common/pkg-config.gypi',
+ ],
+ 'sources': [
+ 'telephony_api.js',
+ 'telephony_backend_ofono.cc',
+ 'telephony_backend_ofono.h',
+ 'telephony_extension.cc',
+ 'telephony_extension.h',
+ 'telephony_instance.cc',
+ 'telephony_instance.h',
+ 'telephony_logging.h',
+ ],
+ },
+ ],
+}
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The W3C Telephony API specification:
+// http://www.w3.org/2012/sysapps/telephony/
+
+// The native part of the extension may or may not cache call objects on their
+// own, depending on the backend. The oFono backend does cache the calls which
+// are handled by oFono as DBUS objects. Because the IPC overhead, call and
+// service objects are also cached by this implementation, and the IPC protocol
+// involves exchanging object id's - except when there is a mismatch, in which
+// case a re-sync is done.
+
+///////////////////////////////////////////////////////////////////////////////
+// Utilities
+///////////////////////////////////////////////////////////////////////////////
+
+var v8tools = null;
+if (typeof requireNative !== 'undefined') {
+ v8tools = requireNative('v8tools');
+}
+
+if (!v8tools) {
+ v8tools = function() {};
+ v8tools.prototype.forceSetProperty = function(obj, prop, value) {
+ obj[prop] = value;
+ };
+}
+
+function addReadonlyProperty(obj, name, value) {
+ Object.defineProperty(obj, name, {
+ configurable: true,
+ enumerable: true,
+ writable: true, // TODO zolkis: fix v8tools.forceSetProperty issues
+ value: value
+ });
+}
+
+function forceSetProperty(obj, prop, value) {
+ v8tools.forceSetProperty(obj, prop, value);
+ //obj[prop] = value;
+}
+
+function error(txt) {
+ var output = (txt instanceof Object) ?
+ '\n[Telephony JS] ' + toPrintableString(txt) :
+ '\n[Telephony JS] Error: ' + (txt || 'null');
+
+ console.log(output);
+}
+
+function log(txt) {
+ console.log('\n[JS log] ' + txt instanceof Object ?
+ toPrintableString(txt) : (txt || 'null'));
+}
+
+function toPrintableString(o) {
+ if (!(o instanceof Object) && !(o instanceof Array))
+ return o;
+ var out = '{ ';
+ for (var i in o) {
+ out += i + ': ' + toPrintableString(o[i]) + ', ';
+ }
+ out += '}';
+ return out;
+}
+
+function checkRemoteParty(str) {
+ return str.match(/[+]?[0-9*#]{1,80}/g) == str;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// exports
+///////////////////////////////////////////////////////////////////////////////
+
+function TelephonyManager() {
+ this.onserviceadded = null;
+ this.onserviceremoved = null;
+ this.ondefaultservicechanged = null;
+ this.oncalladded = null;
+ this.callchanged = null;
+ this.oncallremoved = null;
+ this.onactivecallchanged = null;
+ this.oncallstatechanged = null;
+ this.onemergencynumberschanged = null;
+ addReadonlyProperty(this, 'defaultServiceId', null);
+ addReadonlyProperty(this, 'activeCall', null);
+}
+
+var _telephonyManager = new TelephonyManager();
+exports = _telephonyManager;
+
+///////////////////////////////////////////////////////////////////////////////
+// Local structures, event listeners
+///////////////////////////////////////////////////////////////////////////////
+
+var _next_promise_id = 1;
+var _pending_promises = {};
+
+var _listeners_count = 0;
+var _listeners = {};
+_listeners.serviceadded = [];
+_listeners.serviceremoved = [];
+_listeners.defaultservicechanged = [];
+_listeners.calladded = [];
+_listeners.callremoved = [];
+_listeners.callstatechanged = [];
+_listeners.callchanged = [];
+_listeners.activecallchanged = [];
+
+function addEventListener(kind, callback) {
+ if (typeof callback == 'function' && _listeners[kind] &&
+ _listeners[kind].indexOf(callback) == -1) {
+ _listeners[kind].push(callback);
+ if (_listeners_count++ == 0)
+ enableBackendNotifications();
+ }
+}
+
+function removeEventListener(kind, callback) {
+ if (typeof callback == 'function' && _listeners[kind]) {
+ var i = _listeners[kind].indexOf(callback);
+ if (i >= 0) {
+ _listeners[kind].splice(i, 1);
+ if (--_listeners_count == 0)
+ disableBackendNotifications();
+ }
+ }
+}
+
+function dispatchEvent(event) {
+ if (typeof event == 'object' && _listeners[event.type]) {
+ _listeners[event.type].forEach(function(cb) {
+ if (typeof cb == 'function') {
+ cb(event);
+ }
+ });
+ return true;
+ }
+ return false;
+}
+
+TelephonyManager.prototype.addEventListener = addEventListener;
+TelephonyManager.prototype.removeEventListener = removeEventListener;
+TelephonyManager.prototype.dispatchEvent = dispatchEvent;
+
+///////////////////////////////////////////////////////////////////////////////
+// Message sending and Promises
+///////////////////////////////////////////////////////////////////////////////
+
+function PendingRequest(resolve, reject) {
+ this.resolve = resolve;
+ this.reject = reject;
+}
+
+function sendRequest(msg, resolve, reject) {
+ msg.promiseId = _next_promise_id++;
+ _pending_promises[msg.promiseId] = new PendingRequest(resolve, reject);
+ var req = JSON.stringify(msg);
+ extension.postMessage(req);
+}
+
+function resolvePromise(msg) {
+ var promise = msg.promiseId ? _pending_promises[msg.promiseId] : null;
+ if (promise && promise.resolve)
+ promise.resolve(msg.returnValue);
+}
+
+function rejectPromise(msg) {
+ var promise = msg.promiseId ? _pending_promises[msg.promiseId] : null;
+ if (!promise || !promise.reject)
+ return;
+ // DOMError will be nuked, DOMException kludgy to create, so using Error
+ // with the properties of DOMException
+ // the W3C Telephony spec needs an update on this...
+ var e = new Error(msg.errorMessage);
+ e.code = msg.errorCode; // add a 'code' property
+ // optionally add a name property; e.name = msg.errorName;
+ promise.reject(e);
+}
+
+// Handle replies and notifications from native extension code.
+extension.setMessageListener(function(json) {
+ var msg = JSON.parse(json);
+ switch (msg.cmd) {
+ // Event notifications.
+ case 'activeCallChanged':
+ handleActiveCallChanged(msg);
+ break;
+ case 'callAdded': // incoming or waiting calls
+ handleCallAdded(msg);
+ break;
+ case 'callRemoved': // disconnected calls
+ handleCallRemoved(msg);
+ break;
+ case 'callStateChanged':
+ handleCallStateChanged(msg);
+ break;
+ case 'callChanged':
+ handleCallChanged(msg);
+ break;
+ case 'serviceAdded':
+ handleServiceAdded(msg);
+ break;
+ case 'serviceRemoved':
+ handleServiceRemoved(msg);
+ break;
+ case 'serviceChanged':
+ handleServiceChanged(msg);
+ break;
+ case 'defaultServiceChanged':
+ handleDefaultServiceChanged(msg);
+ break;
+ case 'emergencyNumbersChanged':
+ handleEmergencyNumbersChanged(msg);
+ break;
+
+ // Replies needing postprocessing before resolving the Promise.
+ case 'getServices':
+ getServicesCallback(msg);
+ break;
+ case 'getCalls':
+ getCallsCallback(msg);
+ break;
+ case 'getParticipants':
+ getParticipantsCallback(msg);
+ break;
+
+ // Replies needing no special treatment before resolving the Promise.
+ case 'setDefaultService':
+ case 'setServiceEnabled':
+ case 'dial':
+ case 'accept':
+ case 'disconnect':
+ case 'hold':
+ case 'resume':
+ case 'deflect':
+ case 'transfer':
+ case 'createConference':
+ case 'split':
+ case 'getEmergencyNumbers':
+ case 'emergencyDial':
+ case 'sendTones':
+ case 'startTone':
+ case 'stopTone':
+ msg.isError ? rejectPromise(msg) : resolvePromise(msg);
+ break;
+ default:
+ error('Invalid message from extension: ' + json);
+ }
+});
+
+function enableBackendNotifications() {
+ var msg = {
+ 'cmd': 'enableNotifications'
+ };
+ return sendRequest(msg, null, null); // no Promise
+}
+
+function disableBackendNotifications() {
+ var msg = {
+ 'cmd': 'disableNotifications'
+ };
+ return sendRequest(msg, null, null); // no Promise
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TelephonyCall
+///////////////////////////////////////////////////////////////////////////////
+
+function TelephonyCall(dict) {
+ addReadonlyProperty(this, 'callId', dict.callId || null);
+ addReadonlyProperty(this, 'conferenceId', dict.conferenceId || null);
+ addReadonlyProperty(this, 'remoteParty', dict.remoteParty || null);
+ addReadonlyProperty(this, 'serviceId', dict.serviceId || null);
+ addReadonlyProperty(this, 'state', dict.state || null);
+ addReadonlyProperty(this, 'stateReason', null);
+}
+
+TelephonyCall.prototype.accept = function() {
+ var msg = {
+ 'cmd': 'accept',
+ 'callId': this.callId
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyCall.prototype.disconnect = function() {
+ var msg = {
+ 'cmd': 'disconnect',
+ 'callId': this.callId
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyCall.prototype.hold = function() {
+ var msg = {
+ 'cmd': 'hold',
+ 'callId': this.callId
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyCall.prototype.resume = function() {
+ var msg = {
+ 'cmd': 'resume',
+ 'callId': this.callId
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyCall.prototype.deflect = function(remoteParty) {
+ var msg = {
+ 'cmd': 'deflect',
+ 'callId': this.callId,
+ 'remoteParty': remoteParty
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyCall.prototype.transfer = function(call) {
+ var msg = {
+ 'cmd': 'transfer'
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyCall.prototype.split = function() {
+ var msg = {
+ 'cmd': 'split',
+ 'callId': this.callId
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Call Management
+///////////////////////////////////////////////////////////////////////////////
+
+function handleActiveCallChanged(msg) {
+ var call = msg.call ? new TelephonyCall(msg.call) : null;
+ forceSetProperty(_telephonyManager, 'activeCall', call);
+ var evt = new Event('activecallchanged');
+ _telephonyManager.dispatchEvent(evt);
+ if (_telephonyManager.onactivecallchanged instanceof Function)
+ _telephonyManager.onactivecallchanged(evt);
+}
+
+function handleCallAdded(msg) {
+ var evt = new CustomEvent('calladded');
+ addReadonlyProperty(evt, 'call', new TelephonyCall(msg.call));
+ _telephonyManager.dispatchEvent(evt);
+ if (_telephonyManager.oncalladded instanceof Function)
+ _telephonyManager.oncalladded(evt);
+}
+
+function handleCallStateChanged(msg) {
+ var call = new TelephonyCall(msg.call);
+ if (call.state == 'disconnected' && !call.duration) {
+ // if the backend didn't calculate duration, do it now
+ call.duration = new Date() - new Date(call.startTime); // milliseconds
+ }
+ if (call.callId == _telephonyManager.activeCall.callId)
+ forceSetProperty(_telephonyManager, 'activeCall', call);
+ var evt = new CustomEvent('callstatechanged');
+ addReadonlyProperty(evt, 'call', call);
+ _telephonyManager.dispatchEvent(evt);
+ if (_telephonyManager.oncallstatechanged instanceof Function)
+ _telephonyManager.oncallstatechanged(evt);
+}
+
+// special things to handle: isConference is changed with conf calls
+function handleCallChanged(msg) {
+ if (!msg.call || !msg.changedProperties)
+ return;
+ var call = new TelephonyCall(msg.call);
+ if (call.callId == _telephonyManager.activeCall.callId)
+ forceSetProperty(_telephonyManager, 'activeCall', call);
+ var evt = new CustomEvent('callchanged');
+ addReadonlyProperty(evt, 'call', call);
+ addReadonlyProperty(evt, 'changedProperties', call.changedProperties || []);
+ _telephonyManager.dispatchEvent(evt);
+ if (_telephonyManager.oncallchanged instanceof Function)
+ _telephonyManager.oncallchanged(evt);
+}
+
+function handleCallRemoved(msg) {
+ var evt = new CustomEvent('callremoved');
+ addReadonlyProperty(evt, 'call', new TelephonyCall(msg.call));
+ _telephonyManager.dispatchEvent(evt);
+ if (_telephonyManager.oncallremoved instanceof Function)
+ _telephonyManager.oncallremoved(evt);
+}
+
+TelephonyManager.prototype.getCalls = function() {
+ var msg = {
+ 'cmd': 'getCalls'
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+function getCallsCallback(msg) {
+ if (msg.isError || !(msg.returnValue instanceof Array)) {
+ rejectPromise(msg);
+ return;
+ }
+ var res = [];
+ for (var i = 0; i < msg.returnValue.length; i++) {
+ res.push(new TelephonyCall(msg.returnValue[i]));
+ }
+ msg.returnValue = res;
+ resolvePromise(msg);
+}
+
+TelephonyManager.prototype.dial = function(remoteParty, dialOptions) {
+ return new Promise(function(resolve, reject) {
+ if (!checkRemoteParty(remoteParty)) {
+ var err = new Error('InvalidCharacterError');
+ err.message = 'Invalid remote party';
+ reject(err);
+ return;
+ }
+ var msg = {
+ cmd: 'dial',
+ serviceId: dialOptions && dialOptions.serviceId || null,
+ remoteParty: remoteParty,
+ hideCallerId: dialOptions && dialOptions.hideCallerId || false
+ };
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyManager.prototype.createConference = function() {
+ var msg = {
+ 'cmd': 'createConference'
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyManager.prototype.getParticipants = function(conferenceId) {
+ var msg = {
+ 'cmd': 'getParticipants',
+ 'conferenceId': conferenceId
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+function getParticipantsCallback(msg) {
+ if (msg.isError || !(msg.returnValue instanceof Array)) {
+ rejectPromise(msg);
+ return;
+ }
+ var res = [];
+ for (var i = 0; i < msg.returnValue.length; i++)
+ res.push(new TelephonyCall(msg.returnValue[i]));
+ msg.returnValue = res;
+ resolvePromise(msg);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Tone Management
+///////////////////////////////////////////////////////////////////////////////
+
+TelephonyManager.prototype.sendTones = function(tones, toneOptions) {
+ var msg = {
+ 'cmd': 'sendTones',
+ 'tones': tones,
+ 'serviceId': toneOptions && toneOptions.serviceId || null,
+ 'gap': toneOptions && toneOptions.gap || null,
+ 'duration': toneOptions && toneOptions.duration || null
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyManager.prototype.startTone = function(tone, toneOptions) {
+ var msg = {
+ 'cmd': 'startTone',
+ 'tone': tone,
+ 'serviceId': toneOptions && toneOptions.serviceId || null,
+ 'gap': toneOptions && toneOptions.gap || ''
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+TelephonyManager.prototype.stopTone = function(serviceId, toneOptions) {
+ var msg = {
+ 'cmd': 'stopTone',
+ 'serviceId': toneOptions && toneOptions.serviceId || null
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Emergency Management
+///////////////////////////////////////////////////////////////////////////////
+
+TelephonyManager.prototype.getEmergencyNumbers = function() {
+ var msg = {
+ 'cmd': 'getEmergencyNumbers'
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TelephonyService
+///////////////////////////////////////////////////////////////////////////////
+
+function TelephonyService(dict) {
+ if (!dict)
+ return null;
+ addReadonlyProperty(this, 'serviceId', dict.id || null);
+ addReadonlyProperty(this, 'enabled', !!dict.enabled);
+ addReadonlyProperty(this, 'emergency', dict.emergency);
+ addReadonlyProperty(this, 'protocol', dict.protocol || '');
+ addReadonlyProperty(this, 'serviceType', dict.type || '');
+ addReadonlyProperty(this, 'provider', dict.provider || '');
+ this.displayName = dict.name || '';
+ return this;
+}
+
+TelephonyService.prototype.setServiceEnabled = function(enabled) {
+ var msg = {
+ 'cmd': 'setServiceEnabled',
+ 'serviceId': this.serviceId,
+ 'enabled': enabled ? true : false
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Service Management
+///////////////////////////////////////////////////////////////////////////////
+
+function handleServiceAdded(msg) {
+ var service = new TelephonyService(msg.service);
+ var evt = new CustomEvent('serviceadded');
+ addReadonlyProperty(evt, 'serviceId', service.serviceId);
+ _telephonyManager.dispatchEvent(evt);
+ if (typeof _telephonyManager.onserviceadded == 'function')
+ _telephonyManager.onserviceadded(evt);
+}
+
+function handleServiceChanged(msg) {
+ var evt = new CustomEvent('servicechanged');
+ addReadonlyProperty(evt, 'service', new TelephonyService(msg.service));
+ addReadonlyProperty(evt, 'changedProperties', msg.changedProperties);
+ _telephonyManager.dispatchEvent(evt);
+ if (typeof _telephonyManager.onservicechanged == 'function')
+ _telephonyManager.onservicechanged(evt);
+}
+
+function handleServiceRemoved(msg) {
+ var evt = new CustomEvent('serviceremoved');
+ addReadonlyProperty(evt, 'service', msg.service);
+ _telephonyManager.dispatchEvent(evt);
+ if (typeof _telephonyManager.onserviceremoved == 'function')
+ _telephonyManager.onserviceremoved(evt);
+}
+
+function notifyDefaultServiceChanged() {
+ var evt = new CustomEvent('defaultservicechanged');
+ addReadonlyProperty(evt, 'service', msg.service);
+ _telephonyManager.dispatchEvent(evt);
+ if (typeof _telephonyManager.ondefaultservicechanged == 'function') {
+ _telephonyManager.ondefaultservicechanged(evt);
+ }
+}
+
+function handleDefaultServiceChanged(msg) {
+ forceSetProperty(_telephonyManager, 'defaultServiceId', msg.service.serviceId);
+ notifyDefaultServiceChanged();
+}
+
+TelephonyManager.prototype.getServiceIds = function() {
+ var msg = {
+ 'cmd': 'getServices'
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
+
+function getServicesCallback(msg) {
+ if (msg.isError) {
+ rejectPromise(msg);
+ return;
+ }
+ var arr = msg.returnValue;
+ var res = [];
+ for (var i = 0; i < arr.length; i++) {
+ var service = arr[i];
+ if (!_telephonyManager.defaultServiceId) {
+ forceSetProperty(_telephonyManager, 'defaultServiceId', service.serviceId);
+ notifyDefaultServiceChanged();
+ }
+ res.push(service.serviceId);
+ }
+ msg.returnValue = res;
+ resolvePromise(msg);
+}
+
+TelephonyManager.prototype.setDefaultServiceId = function(serviceId) {
+ var msg = {
+ 'cmd': 'setDefaultServiceId',
+ 'serviceId': serviceId
+ };
+ return new Promise(function(resolve, reject) {
+ sendRequest(msg, resolve, reject);
+ });
+};
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "telephony/telephony_backend_ofono.h"
+#include "telephony/telephony_logging.h"
+
+#include <gio/gio.h>
+#include <time.h>
+#include <uuid/uuid.h>
+
+#include <string>
+#include <sstream>
+
+namespace {
+
+const char kDbusOfonoService[] = "org.ofono";
+const char kDbusOfonoModemManager[] = "org.ofono.Manager";
+const char kDbusOfonoModem[] = "org.ofono.Modem";
+const char kDbusOfonoVoiceCallManager[] = "org.ofono.VoiceCallManager";
+const char kDbusOfonoVoiceCall[] = "org.ofono.VoiceCall";
+const char kDbusOfonoSimManager[] = "org.ofono.SimManager";
+
+const char kCallStateInit[] = "init";
+const char kCallStateActive[] = "active";
+const char kCallStateHeld[] = "held";
+const char kCallStateDialing[] = "dialing";
+const char kCallStateAlerting[] = "alerting";
+const char kCallStateIncoming[] = "incoming";
+const char kCallStateWaiting[] = "waiting";
+const char kCallStateDisconnected[] = "disconnected";
+const char kCallStateConference[] = "conference";
+
+} // namespace
+
+//////////////////////////////////////////////////////////////////////////////
+// TelephonyBackend, DBUS/signal handling
+///////////////////////////////////////////////////////////////////////////////
+
+TelephonyBackend::TelephonyBackend(TelephonyInstance* instance)
+ : instance_(instance), cancellable_(nullptr), default_service_(nullptr),
+ active_call_(nullptr) {
+}
+
+TelephonyBackend::~TelephonyBackend() {
+ DisableSignalHandlers();
+ for (auto i : calls_)
+ delete i;
+ for (auto i : services_)
+ delete i;
+ for (auto i : removed_calls_)
+ delete i;
+}
+
+void TelephonyBackend::SetDBusSignalHandler(const gchar* iface,
+ const gchar* name, const gchar* obj, GDBusSignalCallback cb,
+ gpointer user_data) {
+ LOG_DBG("SetDBusSignalHandler: " << std::string(iface) << ":" <<
+ std::string(name) << " [" << std::string(obj ? obj : "") << "]");
+
+ GDBusConnection* dbus_conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr,
+ nullptr);
+
+ guint handle = g_dbus_connection_signal_subscribe(dbus_conn, nullptr,
+ iface, name, obj, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, cb, user_data,
+ nullptr);
+
+ dbus_listeners_.push_back(handle);
+}
+
+GVariant* TelephonyBackend::SyncDBusCall(const gchar* object,
+ const gchar* interface, const gchar* method, GVariant* parameters,
+ GError** error) {
+ LOG_DBG("SyncDBusCall: " << std::string(method));
+
+ GDBusConnection* dbus_conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr,
+ nullptr);
+
+ return g_dbus_connection_call_sync(dbus_conn, kDbusOfonoService,
+ object, interface, method, parameters,
+ nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, error);
+}
+
+void TelephonyBackend::AsyncDBusCall(const gchar* object,
+ const gchar* interface, const gchar* method, GVariant* parameters,
+ GAsyncReadyCallback callback, gpointer user_data) {
+
+ GDBusConnection* dbus_conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr,
+ nullptr);
+
+ g_dbus_connection_call(dbus_conn, kDbusOfonoService,
+ object, interface, method, parameters,
+ nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, callback, user_data);
+}
+
+void TelephonyBackend::EnableSignalHandlers() {
+ SetDBusSignalHandler(kDbusOfonoModemManager, "ModemAdded", nullptr,
+ SignalHandler, this);
+ SetDBusSignalHandler(kDbusOfonoModemManager, "ModemRemoved", nullptr,
+ SignalHandler, this);
+ SetDBusSignalHandler(kDbusOfonoModem, "PropertyChanged", nullptr,
+ SignalHandler, this);
+
+ SetDBusSignalHandler(kDbusOfonoVoiceCallManager, "CallAdded", nullptr,
+ SignalHandler, this);
+ SetDBusSignalHandler(kDbusOfonoVoiceCallManager, "CallRemoved", nullptr,
+ SignalHandler, this);
+ SetDBusSignalHandler(kDbusOfonoVoiceCallManager, "PropertyChanged", nullptr,
+ SignalHandler, this);
+
+ SetDBusSignalHandler(kDbusOfonoVoiceCall, "PropertyChanged", nullptr,
+ SignalHandler, this);
+ SetDBusSignalHandler(kDbusOfonoVoiceCall, "DisconnectReason", nullptr,
+ SignalHandler, this);
+}
+
+// Called when the last signal listener is removed.
+void TelephonyBackend::DisableSignalHandlers() {
+ LOG_DBG("DisableSignalHandlers");
+ GDBusConnection* dbus_conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr,
+ nullptr);
+ for (int i = 0; i < dbus_listeners_.size(); i++) {
+ g_dbus_connection_signal_unsubscribe(dbus_conn, dbus_listeners_[i]);
+ }
+ dbus_listeners_.clear();
+}
+
+void TelephonyBackend::SignalHandler(GDBusConnection* connection,
+ const gchar* sender, const gchar* object,
+ const gchar* interface, const gchar* signal,
+ GVariant* parameters, gpointer user_data) {
+
+ TelephonyBackend* backend = static_cast<TelephonyBackend*>(user_data);
+ if (!backend) {
+ LOG_ERR("SignalHandler: failed to get backend");
+ return;
+ }
+ LOG_DBG("SignalHandler: " << std::string(signal));
+
+ if (!strcmp(interface, kDbusOfonoModemManager)) {
+ if (!strcmp(signal, "ModemAdded")) {
+ backend->OnModemAdded(object, parameters);
+ } else if (!strcmp(signal, "ModemRemoved")) {
+ backend->OnModemRemoved(object, parameters);
+ }
+ return;
+ }
+
+ if (!strcmp(interface, kDbusOfonoModem)) {
+ backend->OnModemChanged(object, parameters);
+ return;
+ }
+
+ if (!strcmp(interface, kDbusOfonoVoiceCallManager)) {
+ if (!strcmp(signal, "CallAdded")) {
+ backend->OnCallAdded(object, parameters);
+ } else if (!strcmp(signal, "CallRemoved")) {
+ backend->OnCallRemoved(object, parameters);
+ } else if (!strcmp(signal, "PropertyChanged")) {
+ backend->OnCallManagerChanged(object, parameters);
+ }
+ return;
+ }
+
+ if (!strcmp(interface, kDbusOfonoVoiceCall)) {
+ if (!strcmp(signal, "PropertyChanged")) {
+ backend->OnCallChanged(object, parameters);
+ } else if (!strcmp(signal, "DisconnectReason")) {
+ backend->OnCallDisconnectReason(object, parameters);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TelephonyService
+///////////////////////////////////////////////////////////////////////////////
+
+picojson::object& TelephonyBackend::ToJson(TelephonyService* service,
+ picojson::object& out) {
+ out["serviceId"] = picojson::value(service->id);
+ out["name"] = picojson::value(service->name);
+ out["serviceType"] = picojson::value(service->type);
+ out["enabled"] = picojson::value(service->online? true : false);
+ out["provider"] = picojson::value(service->provider);
+ out["protocol"] = picojson::value(service->protocol);
+ out["emergency"] = picojson::value(service->emergency);
+ return out;
+}
+
+void TelephonyBackend::LogService(TelephonyService* service) {
+ std::cout << "{\n\tserviceId: " << service->id;
+ std::cout << "\n\tname: " << service->name;
+ std::cout << "\n\tenabled: " <<
+ (service->powered && service->online ? "true" : "false");
+ std::cout << "\n\tserial: " << service->serial; // e.g. BT address comes here
+ std::cout << "\n}" << std::endl;
+}
+
+// Return the affected JS property name
+const char* TelephonyBackend::UpdateServiceProperty(TelephonyService* service,
+ const char* key, GVariant* value) {
+ if (!strcmp(key, "Powered")) {
+ service->powered = g_variant_get_boolean(value);
+ return "enabled";
+ }
+
+ if (!strcmp(key, "Online")) {
+ service->online = g_variant_get_boolean(value);
+ return "enabled";
+ }
+
+ if (!strcmp(key, "Lockdown")) {
+ service->lockdown = g_variant_get_boolean(value);
+ return nullptr;
+ }
+
+ if (!strcmp(key, "Emergency")) {
+ service->emergency = g_variant_get_boolean(value);
+ return "emergency";
+ }
+
+ if (!strcmp(key, "Manufacturer")) {
+ service->provider = g_variant_get_string(value, nullptr);
+ return "name";
+ }
+
+ if (!strcmp(key, "Name")) {
+ service->name = g_variant_get_string(value, nullptr);
+ return "name";
+ }
+
+ if (!strcmp(key, "Model")) {
+ service->model = g_variant_get_string(value, nullptr);
+ return "name";
+ }
+
+ if (!strcmp(key, "Revision")) {
+ service->revision = g_variant_get_string(value, nullptr);
+ return "name";
+ }
+
+ if (!strcmp(key, "Serial")) {
+ service->serial = g_variant_get_string(value, nullptr);
+ return "name";
+ }
+
+ if (!strcmp(key, "Type")) {
+ const gchar* stype = g_variant_get_string(value, nullptr);
+ if (!strcmp(stype, "hardware"))
+ service->type = "hw";
+ else if (!strcmp(stype, "hfp"))
+ service->type = "hfp";
+ else if (!strcmp(stype, "sap"))
+ service->type = "sap";
+ else
+ service->type = "unknown";
+ return "type";
+ }
+
+ return nullptr;
+}
+
+bool TelephonyBackend::InitService(TelephonyService* service,
+ const std::string& sid, GVariantIter* props) {
+
+ if (sid.empty() || !props) {
+ return false;
+ }
+
+ service->id = sid;
+ service->protocol = "gsm";
+
+ char* key = nullptr;
+ GVariant* value = nullptr;
+ while (g_variant_iter_next(props, "{sv}", &key, &value)) {
+ UpdateServiceProperty(service, key, value);
+ }
+ g_variant_unref(value);
+
+ return true;
+}
+
+void TelephonyBackend::UpdateService(TelephonyService* service) {
+ GError* err = nullptr;
+ GVariant* res = SyncDBusCall(IdToDbus(service->id).c_str(),
+ kDbusOfonoModemManager, "GetProperties", nullptr, &err);
+
+ if (err) {
+ LOG_ERR("UpdateService: " << err->message);
+ return;
+ }
+
+ GVariantIter* props = nullptr;
+ g_variant_get(res, "(a{sv})", &props);
+ InitService(service, service->id, props);
+ g_variant_iter_free(props);
+ g_variant_unref(res);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TelephonyCall
+///////////////////////////////////////////////////////////////////////////////
+
+picojson::object& TelephonyBackend::ToJson(TelephonyCall* call,
+ picojson::object& out) {
+ out["callId"] = picojson::value(call->id);
+ out["serviceId"] = picojson::value(call->service ?
+ call->service->id :
+ "");
+ out["remoteParty"] = picojson::value(call->remote_party);
+ out["state"] = picojson::value(call->state);
+ out["stateReason"] = picojson::value(call->state_reason);
+ out["startTime"] = picojson::value(call->start_time);
+ out["duration"] = picojson::value(call->duration);
+ out["protocol"] = picojson::value(call->protocol);
+ out["emergency"] = picojson::value(call->emergency);
+ out["conferenceId"] = picojson::value(call->conference ?
+ call->conference->id : "");
+ picojson::value::array p;
+ for (auto c : call->participants) {
+ if (c)
+ p.push_back(picojson::value(c->id));
+ }
+ out["participants"] = picojson::value(p);
+ return out;
+}
+
+void TelephonyBackend::LogCall(TelephonyCall* call) {
+ std::cout << "{\n\tcallId: " << call->id;
+ std::cout << "\n\tremoteParty: " << call->remote_party;
+ std::cout << "\n\tstate: " << call->state;
+ std::cout << "\n\tserviceId: " << call->service->id;
+ std::cout << "\n\tstartTime: " << call->start_time;
+ std::cout << "\n}" << std::endl;
+}
+
+void TelephonyBackend::UpdateDuration(TelephonyCall* call) {
+ time_t endt = time(nullptr);
+ struct tm tm;
+ // time format from <ofono source>/src/voicecall.c
+ strptime(call->start_time.c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm);
+ time_t start_time = mktime(&tm);
+ call->duration = difftime(endt, start_time) * 1000.0; // sec --> msec
+}
+
+const char* TelephonyBackend::UpdateCallState(TelephonyCall* call,
+ std::string& state) {
+ if (state == kCallStateDisconnected) { // stamp end time
+ UpdateDuration(call);
+ call->state = state;
+ return "state";
+ }
+
+ TelephonyCall* conf = call->conference;
+ // Check on conference participants state change.
+ if (conf && call != conf) {
+ // Calls participating in a conference stay in 'conference' state, but
+ // the shadow state is updated.
+ // Note that with oFono objects, all connected calls participating in a
+ // multiparty call are in 'active' or 'held' state, however, the W3C spec
+ // treats them as 'conference' state. When the state of a participating
+ // call object is changed, it is tracked separately, and the conference
+ // call state inherits the shadow state of participating calls.
+ call->saved_state = state;
+
+ if (state != conf->state) {
+ bool change_conf_state = false;
+ if (state == kCallStateActive) { // at least one active participant
+ change_conf_state = true;
+ } else if (state == kCallStateHeld) {
+ // all participating calls must be held in order to change
+ // the state of the conference calls
+ change_conf_state = true;
+ for (auto p : conf->participants) {
+ if (p && p->state != kCallStateHeld)
+ change_conf_state = false;
+ }
+ }
+
+ if (change_conf_state) {
+ conf->state = state;
+ NotifyCallChanged("state", conf);
+ return nullptr; // no notification for shadow state change
+ }
+ }
+ }
+
+ call->state = state;
+ return "state";
+}
+
+// Return the affected JS property name.
+const char* TelephonyBackend::UpdateCallProperty(TelephonyCall* call,
+ const char* key, GVariant* value) {
+
+ if (!strcmp(key, "State")) {
+ std::string state = g_variant_get_string(value, nullptr);
+ return UpdateCallState(call, state);
+ }
+
+ if (!strcmp(key, "StartTime")) {
+ call->start_time = g_variant_get_string(value, nullptr);
+ return "startTime";
+ }
+
+ if (!strcmp(key, "LineIdentification")) {
+ call->remote_party = g_variant_get_string(value, nullptr);
+ return "remoteParty";
+ }
+
+ if (!strcmp(key, "Name")) {
+ call->name = g_variant_get_string(value, nullptr);
+ return "name";
+ }
+
+ if (!strcmp(key, "Multiparty")) {
+ // this is the only notification from oFono for conference participants
+ if (!g_variant_get_boolean(value) && call->conference) {
+ // the call goes back to normal, split from conference
+ call->conference = nullptr;
+ call->state = call->saved_state.empty() ?
+ kCallStateActive : call->saved_state;
+ call->saved_state.clear();
+ }
+ // when 'value' is true, createConference() has already updated this call
+ // only need to trigger the state change notification from here
+ return "state";
+ }
+
+ if (!strcmp(key, "Emergency")) {
+ call->emergency = g_variant_get_boolean(value);
+ return "emergency";
+ }
+}
+
+bool TelephonyBackend::InitCall(TelephonyCall* call, const std::string& cid,
+ GVariantIter* props) {
+
+ if (cid.empty() || !props) {
+ return false;
+ }
+
+ call->id = cid;
+ call->protocol = "gsm";
+ call->duration = 0;
+ call->state = kCallStateInit;
+ char* key = nullptr;
+ GVariant* value = nullptr;
+ while (g_variant_iter_next(props, "{sv}", &key, &value)) {
+ UpdateCallProperty(call, key, value);
+ }
+ g_variant_unref(value);
+
+ if (!call->service) {
+ // extract service id from call id
+ // format is <service_id>/voicecall<xx>
+ size_t i = cid.find("voicecall");
+ if (i <= 0)
+ return 0;
+
+ std::string sid = cid.substr(0, i - 1);
+ call->service = FindService(sid);
+ if (!call->service) {
+ if (!QueryServices())
+ return false;
+ call->service = FindService(sid);
+ if (!call->service)
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void TelephonyBackend::UpdateCall(TelephonyCall* call) {
+ GError* err = nullptr;
+ GVariant* res = SyncDBusCall(IdToDbus(call->id).c_str(), kDbusOfonoVoiceCall,
+ "GetProperties", nullptr, &err);
+ if (err) {
+ LOG_ERR("UpdateCall: " << err->message);
+ return;
+ }
+
+ GVariantIter* props = nullptr;
+ g_variant_get(res, "(a{sv})", &props);
+ InitCall(call, call->id, props);
+ g_variant_iter_free(props);
+ g_variant_unref(res);
+}
+
+void TelephonyBackend::LogServices() {
+ std::cout << "\nServices:";
+ for (int i = 0; i < services_.size(); i++)
+ LogService(services_[i]);
+}
+
+void TelephonyBackend::LogCalls() {
+ std::cout << "\nCalls:";
+ for (int i = 0; i < calls_.size(); i++)
+ LogCall(calls_[i]);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// TelephonyBackend, service related notifications
+///////////////////////////////////////////////////////////////////////////////
+
+// In most cases there is only one or two services.
+TelephonyService* TelephonyBackend::FindService(const std::string& id) {
+ for (auto s : services_) {
+ if (s && s->id == id) {
+ return s;
+ }
+ }
+ return nullptr;
+}
+
+void TelephonyBackend::OnModemAdded(const gchar* obj, GVariant* parameters) {
+ LOG_DBG("OnModemAdded: " << std::string(obj));
+ const char* path = nullptr;
+ GVariantIter* props = nullptr;
+ g_variant_get(parameters, "(oa{sv})", &path, &props);
+ if (!path) {
+ LOG_ERR("OnModemAdded: invalid object.");
+ return;
+ }
+
+ std::string sid = IdFromDbus(path);
+ RemoveService(FindService(sid));
+ TelephonyService* service = new TelephonyService();
+ InitService(service, sid, props);
+
+ services_.push_back(service);
+ NotifyServiceAdded(service);
+ g_variant_iter_free(props);
+}
+
+void TelephonyBackend::OnModemChanged(const gchar* obj, GVariant* parameters) {
+ LOG_DBG("OnModemChanged: " << std::string(obj));
+ TelephonyService* service = FindService(IdFromDbus(obj));
+ if (!service) {
+ LOG_ERR("OnModemChanged: could not find service.");
+ return;
+ }
+
+ const gchar* key = nullptr;
+ GVariant* value = nullptr;
+ g_variant_get(parameters, "(sv)", &key, &value);
+ key = UpdateServiceProperty(service, key, value);
+ if (key)
+ NotifyServiceChanged(key, service);
+ g_variant_unref(value);
+}
+
+void TelephonyBackend::OnModemRemoved(const gchar* obj, GVariant* parameters) {
+ LOG_DBG("OnModemRemoved: " << std::string(obj));
+ char* path = nullptr;
+ g_variant_get(parameters, "(o)", &path);
+ TelephonyService* service = FindService(IdFromDbus(path));
+
+ if (!service) {
+ LOG_ERR("OnModemRemoved: could not find service.");
+ return;
+ }
+
+ NotifyServiceRemoved(service);
+ RemoveService(service);
+}
+
+void TelephonyBackend::NotifyDefaultServiceChanged() {
+ LOG_DBG("NotifyDefaultServiceChanged");
+ picojson::object js, notif;
+ notif["cmd"] = picojson::value("defaultServiceChanged");
+ notif["service"] = default_service_ ?
+ picojson::value(ToJson(default_service_, js)) :
+ picojson::value();
+ instance_->SendNotification(picojson::value(notif));
+}
+
+void TelephonyBackend::NotifyServiceAdded(TelephonyService* service) {
+ LOG_DBG("NotifyServiceAdded");
+ picojson::object js, notif;
+ notif["cmd"] = picojson::value("serviceAdded");
+ notif["service"] = service ?
+ picojson::value(ToJson(service, js)) :
+ picojson::value();
+ instance_->SendNotification(picojson::value(notif));
+}
+
+void TelephonyBackend::NotifyServiceChanged(const char* key,
+ TelephonyService* service) {
+ LOG_DBG("NotifyServiceChanged: " << std::string(key));
+ picojson::array changedProps;
+ changedProps.push_back(picojson::value(key));
+ picojson::object js, notif;
+ notif["cmd"] = picojson::value("serviceChanged");
+ notif["changedProperties"] = picojson::value(changedProps);
+ notif["service"] = service ?
+ picojson::value(ToJson(service, js)) :
+ picojson::value();
+ instance_->SendNotification(picojson::value(notif));
+}
+
+void TelephonyBackend::NotifyServiceRemoved(TelephonyService* service) {
+ LOG_DBG("NotifyServiceRemoved");
+ picojson::object js, notif;
+ notif["cmd"] = picojson::value("serviceRemoved");
+ notif["service"] = service ?
+ picojson::value(ToJson(service, js)) :
+ picojson::value();
+ instance_->SendNotification(picojson::value(notif));
+}
+
+void TelephonyBackend::RemoveService(TelephonyService* service) {
+ for (auto iter = services_.begin(); iter != services_.end();) {
+ if (*iter == service) {
+ delete *iter;
+ iter = services_.erase(iter);
+ }
+ }
+}
+
+void TelephonyBackend::OnCallManagerChanged(const gchar* obj,
+ GVariant* parameters) {
+ // 'parameters' contains emergency numbers list a(s)
+ LOG_DBG("OnCallManagerChanged");
+ GVariantIter* prop_iter = nullptr;
+ GVariant* value = nullptr;
+ picojson::value::array results;
+ g_variant_get(parameters, "(as)", &prop_iter);
+
+ while (g_variant_iter_next(prop_iter, "s", &value)) {
+ const gchar* num = g_variant_get_string(value, nullptr);
+ results.push_back(picojson::value(num));
+ }
+ g_variant_iter_free(prop_iter);
+
+ picojson::object notif;
+ notif["cmd"] = picojson::value("emergencyNumbersChanged");
+ notif["returnValue"] = picojson::value(results);
+ instance_->SendNotification(picojson::value(notif));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// TelephonyBackend, call related notifications
+///////////////////////////////////////////////////////////////////////////////
+
+// In most cases there is only one or two calls.
+TelephonyCall* TelephonyBackend::FindCall(const std::string& id) {
+ for (auto call : calls_) {
+ if (call && (call->id == id))
+ return call;
+ }
+ return nullptr;
+}
+
+void TelephonyBackend::OnCallAdded(const gchar* obj, GVariant* parameters) {
+ std::string sid = IdFromDbus(obj);
+ TelephonyService* service = FindService(sid);
+
+ if (!service) {
+ QueryServices();
+ service = FindService(sid);
+ if (!service) {
+ LOG_ERR("OnCallAdded: invalid service id = " << sid);
+ return;
+ }
+ }
+
+ char* path = nullptr;
+ GVariantIter* props = nullptr;
+ g_variant_get(parameters, "(oa{sv})", &path, &props);
+ if (!path) {
+ LOG_ERR("OnCallAdded: invalid object path.");
+ return;
+ }
+
+ std::string cid = IdFromDbus(path);
+ TelephonyCall* call = FindCall(cid);
+ if (!call) {
+ call = new TelephonyCall(service);
+ InitCall(call, cid, props);
+ calls_.push_back(call);
+ } else {
+ LOG_ERR("OnCallAdded: duplicate call id found.");
+ call->service = service;
+ InitCall(call, cid, props);
+ }
+
+ NotifyCallAdded(call);
+ LOG_DBG("OnCallAdded: " << call->id);
+ g_variant_iter_free(props);
+}
+
+void TelephonyBackend::OnCallChanged(const gchar* obj, GVariant* parameters) {
+ TelephonyCall* call = FindCall(IdFromDbus(obj));
+ if (!call) {
+ LOG_ERR("OnCallChanged: invalid object.");
+ return;
+ }
+
+ const gchar* key = nullptr;
+ GVariant* value = nullptr;
+ g_variant_get(parameters, "(sv)", &key, &value);
+ key = UpdateCallProperty(call, key, value);
+ g_variant_unref(value);
+ if (!key)
+ return;
+
+ NotifyCallChanged(key, call);
+ LOG_DBG("OnCallChanged: " << call->id << "[" << key << "]");
+}
+
+void TelephonyBackend::OnCallDisconnectReason(const gchar* obj,
+ GVariant* parameters) {
+ LOG_DBG("OnCallDisconnectReason: " << std::string(obj));
+ TelephonyCall* call = FindCall(IdFromDbus(obj));
+
+ if (!call) {
+ LOG_ERR("OnCallDisconnectReason: invalid object.");
+ return;
+ }
+
+ gchar* result = nullptr;
+ g_variant_get(parameters, "(s)", &result);
+ call->state_reason = result;
+ NotifyCallChanged("stateReason", call);
+ g_free(result);
+}
+
+void TelephonyBackend::OnCallRemoved(const gchar* obj, GVariant* parameters) {
+ LOG_DBG("OnCallRemoved: " << std::string(obj));
+ char* path = nullptr;
+ g_variant_get(parameters, "(o)", &path);
+ TelephonyCall* call = FindCall(IdFromDbus(path));
+
+ if (!call) {
+ LOG_ERR("OnCallRemoved: could not find call.");
+ return;
+ }
+
+ RemoveCall(call);
+ // When the last call is removed, the conf call and all others are purged.
+ if (calls_.empty()) {
+ for (auto iter = removed_calls_.begin(); iter != removed_calls_.end();) {
+ delete *iter;
+ iter = removed_calls_.erase(iter);
+ }
+ }
+}
+
+void TelephonyBackend::CheckActiveCall(TelephonyCall* call) {
+ if (!call) { // call has been removed and need to find the next active call
+ bool changed = false;
+ for (auto c : calls_) {
+ if (c && c->state == kCallStateActive &&
+ (!c->conference || c->id == c->conference->id)) {
+ active_call_ = c;
+ changed = true;
+ }
+ }
+
+ if (!changed)
+ active_call_ = nullptr;
+ } else if (active_call_ && call->id == active_call_->id &&
+ call->state != kCallStateActive) { // stop being active
+ active_call_ = nullptr;
+ } else if (active_call_ == call || call->state != kCallStateActive ||
+ call->conference && call->conference->id != call->id) {
+ // either no change, or not active, or participant in a conference
+ // participants in a conference won't be active_call_, only the conf call
+ return;
+ } else { // an active [conf] call different from the current active_call_
+ active_call_ = call;
+ }
+
+ LOG_DBG("CheckActiveCall: active call set to " <<
+ (active_call_ ? active_call_->id : "none."));
+ picojson::object js, notif;
+ notif["cmd"] = picojson::value("activeCallChanged");
+ notif["call"] = active_call_ ?
+ picojson::value(ToJson(active_call_, js)) :
+ picojson::value();
+ instance_->SendNotification(picojson::value(notif));
+}
+
+void TelephonyBackend::NotifyCallAdded(TelephonyCall* call) {
+ LOG_DBG("NotifyCallAdded: " << call->id);
+ picojson::object js, notif;
+ notif["cmd"] = picojson::value("callAdded");
+ notif["call"] = call ?
+ picojson::value(ToJson(call, js)) :
+ picojson::value();
+ instance_->SendNotification(picojson::value(notif));
+}
+
+void TelephonyBackend::NotifyCallChanged(const char* key, TelephonyCall* call) {
+ LOG_DBG("NotifyCallChanged: " << call->id << "[" << std::string(key) << "]");
+ picojson::array changed_props; // for compatibility with other backends
+ changed_props.push_back(picojson::value(key));
+
+ picojson::object js, notif;
+ bool statechange = (key == "state");
+ notif["cmd"] = statechange ?
+ picojson::value("callStateChanged") :
+ picojson::value("callChanged");
+ notif["changedProperties"] = picojson::value(changed_props);
+ notif["call"] = call ?
+ picojson::value(ToJson(call, js)) :
+ picojson::value();
+
+ instance_->SendNotification(picojson::value(notif));
+
+ if (statechange)
+ CheckActiveCall(call);
+}
+
+void TelephonyBackend::NotifyCallRemoved(TelephonyCall* call) {
+ LOG_DBG("NotifyCallRemoved: " << call->id);
+ picojson::object js, notif;
+ notif["cmd"] = picojson::value("callRemoved");
+ notif["call"] = call ?
+ picojson::value(ToJson(call, js)) :
+ picojson::value();
+ instance_->SendNotification(picojson::value(notif));
+}
+
+void TelephonyBackend::RemoveCall(TelephonyCall* call) {
+ if (!call) {
+ LOG_ERR(("RemoveCall: null call"));
+ return;
+ }
+
+ LOG_DBG("RemoveCall: removing " << call->id);
+ // remove from the global call list, and references from other calls
+ for (auto iter = calls_.begin(); iter != calls_.end();) {
+ TelephonyCall* c = *iter;
+ if (c != call && c == call->conference) {
+ RemoveParticipant(c, call);
+ LOG_DBG("RemoveCall: removed from conference " << c->id);
+ call->conference = nullptr;
+ }
+
+ if (c->id == call->id) {
+ LOG_DBG("RemoveCall: removed from call list: " << c->id);
+ iter = calls_.erase(iter);
+ removed_calls_.push_back(call);
+ NotifyCallRemoved(call);
+ CheckActiveCall();
+ } else {
+ ++iter;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// TelephonyBackend, API calls
+///////////////////////////////////////////////////////////////////////////////
+
+void TelephonyBackend::GetServices(const picojson::value& msg) {
+ LOG_DBG("GetServices");
+ if (!QueryServices()) {
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ picojson::value::array results;
+ for (int i = 0; i < services_.size(); i++) {
+ if (services_[i]) {
+ picojson::object js;
+ ToJson(services_[i], js);
+ results.push_back(picojson::value(js));
+ }
+ }
+
+ instance_->SendSuccessReply(msg, picojson::value(results));
+}
+
+bool TelephonyBackend::QueryServices() {
+ LOG_DBG("QueryServices");
+ GError* err = nullptr;
+ GVariant* res =
+ SyncDBusCall("/", kDbusOfonoModemManager, "GetModems", nullptr, &err);
+
+ if (err) {
+ LOG_ERR("QueryServices: " << err->message);
+ g_error_free(err);
+ return false;
+ }
+
+ if (!res)
+ return true; // no services found, that's OK
+
+ GVariantIter* modems;
+ g_variant_get(res, "(a(oa{sv}))", &modems);
+ char* path;
+ GVariantIter* props;
+ while (g_variant_iter_next(modems, "(oa{sv})", &path, &props)) {
+ std::string sid = IdFromDbus(path);
+ TelephonyService* service = FindService(sid);
+ bool found = !!service;
+ if (!service)
+ service = new TelephonyService();
+ InitService(service, sid, props);
+ if (!found)
+ services_.push_back(service);
+ }
+ g_variant_iter_free(props);
+ g_variant_iter_free(modems);
+ g_variant_unref(res);
+
+ // set a default service
+ if (!default_service_ && services_.size() > 0) {
+ default_service_ = services_[0];
+ NotifyDefaultServiceChanged();
+ }
+
+ return true;
+}
+
+void TelephonyBackend::SetServiceEnabled(const picojson::value& msg) {
+ TelephonyService* service = FindService(msg.get("serviceId").to_str());
+ if (!service) {
+ LOG_ERR("TelephonyService not found.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (SetModemEnabled(service, msg.get("enabled").get<bool>()))
+ instance_->SendSuccessReply(msg);
+ else
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+}
+
+bool TelephonyBackend::SetModemEnabled(TelephonyService* service,
+ bool enabled) {
+ LOG_DBG("SetModemEnabled: " << service->id << " : " << enabled);
+ std::string path = IdToDbus(service->id);
+
+ GError* err = nullptr;
+ SyncDBusCall(path.c_str(), kDbusOfonoModemManager, "SetProperty",
+ g_variant_new("(sv)", "Powered", g_variant_new("b", enabled)), &err);
+
+ if (!err)
+ SyncDBusCall(path.c_str(), kDbusOfonoModemManager, "SetProperty",
+ g_variant_new("(sv)", "Online", g_variant_new("b", enabled)), &err);
+
+ if (err) {
+ LOG_ERR("SetModemEnabled: " << err->message);
+ g_error_free(err);
+ return false;
+ }
+
+ return true;
+}
+
+void TelephonyBackend::SetDefaultService(const picojson::value& msg) {
+ TelephonyService* service = FindService(msg.get("serviceId").to_str());
+
+ if (!service) {
+ LOG_ERR("TelephonyService not found.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("SetDefaultService: " << service->id);
+ if (!service->online)
+ SetModemEnabled(service, true);
+
+ default_service_ = service;
+ // No need to also notify, the Promise will confirm the action.
+ instance_->SendSuccessReply(msg);
+}
+
+void TelephonyBackend::GetDefaultService(const picojson::value& msg) {
+ std::string dsi = (default_service_ ? default_service_->id : "");
+ instance_->SendSuccessReply(msg, picojson::value(dsi));
+}
+
+// Get the calls from all services.
+void TelephonyBackend::GetCalls(const picojson::value& msg) {
+ LOG_DBG("GetCalls");
+ for (int i = 0; i < services_.size(); i++) {
+ if (!services_[i]->online)
+ continue;
+
+ std::string path = IdToDbus(services_[i]->id);
+ GError* err = nullptr;
+ GVariant* res = SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager,
+ "GetCalls", nullptr, &err);
+
+ if (err) {
+ LOG_ERR("GetCalls: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ if (!res) {
+ LOG_ERR("Results from 'GetCalls' method is nullptr");
+ instance_->SendSuccessReply(msg, picojson::value(""));
+ return;
+ }
+
+ GVariantIter* calls;
+ g_variant_get(res, "(a(oa{sv}))", &calls);
+
+ char* obj;
+ GVariantIter* props;
+ picojson::value::array results;
+ while (g_variant_iter_next(calls, "(oa{sv})", &obj, &props)) {
+ std::string cid = IdFromDbus(obj);
+ TelephonyCall* call = FindCall(cid);
+ if (!call) {
+ call = new TelephonyCall(services_[i]);
+ InitCall(call, cid, props);
+ calls_.push_back(call);
+ }
+ }
+
+ for (auto c : calls_) {
+ picojson::object js;
+ ToJson(c, js);
+ results.push_back(picojson::value(js));
+ }
+
+ instance_->SendSuccessReply(msg, picojson::value(results));
+ g_variant_iter_free(calls);
+ g_variant_unref(res);
+ }
+}
+
+void TelephonyBackend::DialCall(const picojson::value& msg) {
+ TelephonyService* service = FindService(msg.get("serviceId").to_str());
+
+ if (!service) {
+ // the app has called withouth querying services, expecting default service
+ if (!default_service_) {
+ if (!QueryServices() && !default_service_) { // query affects default
+ LOG_ERR("Dial: unable to find telephony services.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+ }
+ service = default_service_;
+ }
+
+ std::string remote = msg.get("remoteParty").to_str();
+ if (!CheckRemoteParty(remote)) {
+ LOG_ERR("Dial: invalid remote party " << remote);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("Dial " << remote);
+ GError* err = nullptr;
+ std::string sid = IdToDbus(service->id);
+ GVariant* res = SyncDBusCall(sid.c_str(),
+ kDbusOfonoVoiceCallManager,
+ "Dial",
+ g_variant_new("(ss)",
+ remote.c_str(),
+ msg.get("hideCallerId").get<bool>() ?
+ "enabled" : ""),
+ &err);
+
+ if (err) {
+ LOG_ERR("Dial: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ if (!res) {
+ LOG_ERR("Dial: nullptr call object.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ char* obj = nullptr;
+ g_variant_get(res, "(o)", &obj);
+ std::string cid = IdFromDbus(obj);
+ TelephonyCall* call = FindCall(cid);
+
+ // The call should already be there, added by OnCallAdded
+ if (!call) {
+ call = new TelephonyCall(service);
+ call->id = cid;
+ UpdateCall(call);
+ }
+
+ picojson::object js;
+ ToJson(call, js);
+ instance_->SendSuccessReply(msg, picojson::value(js));
+ g_variant_unref(res);
+}
+
+void TelephonyBackend::DeflectCall(const picojson::value& msg) {
+ std::string cid = msg.get("callId").to_str();
+ TelephonyCall* call = FindCall(cid);
+
+ if (!call) {
+ LOG_ERR("Accept: invalid call id " << cid);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ std::string remote = msg.get("remoteParty").to_str();
+ LOG_DBG("Deflect " << remote);
+
+ if (!CheckRemoteParty(remote)) {
+ LOG_ERR("Deflect: invalid remote party " << remote);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (call->state != kCallStateIncoming &&
+ call->state != kCallStateWaiting) {
+ LOG_ERR("Deflect: invalid call state: " << call->state);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ GError* err = nullptr;
+ SyncDBusCall(IdToDbus(call->id).c_str(), kDbusOfonoVoiceCall, "Deflect",
+ g_variant_new("s", remote.c_str()), &err);
+
+ if (err) {
+ LOG_ERR("Deflect: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ instance_->SendSuccessReply(msg);
+}
+
+void TelephonyBackend::AcceptCall(const picojson::value& msg) {
+ std::string cid = msg.get("callId").to_str();
+ TelephonyCall* call = FindCall(cid);
+
+ if (!call) {
+ LOG_ERR("Accept: invalid call id " << cid);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("Accept " << call->id);
+ GError* err = nullptr;
+ std::string path = IdToDbus(call->id);
+ SyncDBusCall(path.c_str(), kDbusOfonoVoiceCall, "Answer", nullptr, &err);
+
+ if (err) {
+ LOG_ERR("Accept: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ instance_->SendSuccessReply(msg);
+}
+
+bool TelephonyBackend::HangupCall(std::string& call_id) {
+ LOG_DBG("Disconnect " << call_id);
+ GError* err = nullptr;
+ std::string path = IdToDbus(call_id);
+ SyncDBusCall(path.c_str(), kDbusOfonoVoiceCall, "Hangup", nullptr, &err);
+
+ if (err) {
+ LOG_ERR("Disconnect: " << err->message);
+ g_error_free(err);
+ return false;
+ }
+
+ return true;
+}
+
+void TelephonyBackend::HangupAllCalls(std::string& service_id) {
+ LOG_DBG("Disconnect all calls on service " << service_id);
+ GError* err = nullptr;
+ std::string path = IdToDbus(service_id);
+ SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager, "HangupAll", nullptr,
+ &err);
+
+ if (err) {
+ LOG_ERR("Disconnect all: " << err->message);
+ g_error_free(err);
+ }
+}
+
+void TelephonyBackend::DisconnectCall(const picojson::value& msg) {
+ std::string cid = msg.get("callId").to_str();
+ TelephonyCall* call = FindCall(cid);
+
+ if (!call) {
+ LOG_ERR("Disconnect: invalid call id " << cid);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (!call->conference) {
+ if (HangupCall(cid))
+ instance_->SendSuccessReply(msg);
+ else
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ UpdateDuration(call);
+ call->state = kCallStateDisconnected;
+ call->state_reason = "local";
+
+ bool failed = false;
+ for (auto c : call->participants) {
+ if (!HangupCall(c->id)) // causes call state updates
+ failed = true;
+ }
+
+ if (failed) {
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ HangupAllCalls(call->service->id); // avoid spurious calls
+ return;
+ }
+
+ RemoveCall(call);
+ instance_->SendSuccessReply(msg);
+}
+
+void TelephonyBackend::HoldCall(const picojson::value& msg) {
+ std::string cid = msg.get("callId").to_str();
+ TelephonyCall* call = FindCall(cid);
+
+ if (!call || !call->service) {
+ LOG_ERR("Hold: invalid call id " << cid);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("Hold " << call->id);
+ // oFono has SwapCalls, and HoldAndAnswer. Find out which one to use.
+ const gchar* method = nullptr;
+ // cannot hold held, dialing, alerting, and disconnected calls
+ if (call->state != kCallStateIncoming &&
+ call->state != kCallStateActive &&
+ call->state != kCallStateWaiting) {
+ LOG_ERR("Hold: invalid call state: " << call->state);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (call->state == kCallStateActive) {
+ // check if there are waiting calls
+ for (int i = 0; i < calls_.size(); i++) {
+ if (calls_[i]->state == kCallStateWaiting) {
+ method = "HoldAndAnswer";
+ break;
+ }
+ }
+ }
+
+ if (!method)
+ method = "SwapCalls";
+
+ GError* err = nullptr;
+ std::string path = IdToDbus(call->service->id);
+ SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager, method, nullptr, &err);
+
+ if (err) {
+ LOG_ERR("Hold: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ instance_->SendSuccessReply(msg);
+}
+
+void TelephonyBackend::ResumeCall(const picojson::value& msg) {
+ std::string cid = msg.get("callId").to_str();
+ TelephonyCall* call = FindCall(cid);
+
+ if (!call) {
+ LOG_ERR("Resume: invalid call id");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("Resume " << call->id);
+ if (call->state != kCallStateHeld) {
+ LOG_ERR("Resume: invalid call state '" << call->state);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ GError* err = nullptr;
+ std::string path = IdToDbus(call->service->id);
+ SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager, "SwapCalls",
+ nullptr, &err);
+
+ if (err) {
+ LOG_ERR("Resume: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ instance_->SendSuccessReply(msg);
+}
+
+void TelephonyBackend::TransferCall(const picojson::value& msg) {
+ if (!default_service_) {
+ LOG_ERR("Transfer: no default service");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("Transfer: " << default_service_->id);
+ GError* err = nullptr;
+ std::string path = IdToDbus(default_service_->id);
+ SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager, "Transfer",
+ nullptr, &err);
+
+ if (err) {
+ LOG_ERR("Transfer: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ instance_->SendSuccessReply(msg);
+}
+
+void TelephonyBackend::CreateConference(const picojson::value& msg) {
+ static uint64_t conference_id_;
+ TelephonyService* service = active_call_?
+ active_call_->service :
+ default_service_;
+
+ if (!service) {
+ LOG_ERR("CreateConference: unable to find the telephony service.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("CreateConference: " << service->id);
+ GError* err = nullptr;
+ std::string modem_path = IdToDbus(service->id);
+ GVariant* res = SyncDBusCall(modem_path.c_str(), kDbusOfonoVoiceCallManager,
+ "CreateMultiparty", nullptr, &err);
+
+ const time_t start_time = time(nullptr);
+ if (err) {
+ LOG_ERR("CreateConference: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ TelephonyCall* conf = new TelephonyCall(service);
+
+ // oFono returns the new array with the participating call object paths.
+ // Conference control calls should not be logged, but play safe for clients
+ // and record start time for conference.
+ struct tm tm_s = {0};
+ char timebuf[40];
+ strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S%z",
+ localtime_r(&start_time, &tm_s));
+ conf->start_time = std::string(timebuf);
+
+ conference_id_++;
+ std::stringstream ss;
+ ss << "conference-" << conference_id_;
+ conf->id = ss.str();
+
+ conf->conference = conf;
+ conf->duration = 0;
+ conf->state = kCallStateActive;
+
+ GVariantIter* calls;
+ g_variant_get(res, "(ao)", &calls);
+
+ char* obj = nullptr;
+ while (g_variant_iter_next(calls, "o", &obj)) {
+ std::string call_id = IdFromDbus(obj);
+ TelephonyCall* call = FindCall(call_id);
+
+ if (!call) { // unlikely
+ LOG_ERR("CreateConference: could not find call with id" << call_id);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_variant_iter_free(calls);
+ RemoveCall(conf); // will not cause notification, just cleanup
+ return;
+ }
+
+ call->conference = conf;
+ call->saved_state = call->state;
+ call->state = kCallStateConference; // hangup/hold/split can change state
+ conf->participants.push_back(call);
+ }
+ g_variant_iter_free(calls);
+
+ calls_.push_back(conf);
+ LOG_DBG("Conference call added: " << conf->id);
+
+ picojson::object js;
+ ToJson(conf, js);
+ // No need to NotifyCallAdded(conf), since the Promise returns it.
+ instance_->SendSuccessReply(msg, picojson::value(js));
+ CheckActiveCall(conf);
+ // no need to send notification about the changed participating calls
+ // other than a state change event;
+ // oFono will send CallChanged event with 'Multiparty' property updated
+ // to each participating call object, handle that in UpdateCallProperty
+ for (auto c : conf->participants)
+ NotifyCallChanged("state", c);
+}
+
+void TelephonyBackend::GetConferenceParticipants(const picojson::value& msg) {
+ // This can also be implemented purely in JavaScript.
+ TelephonyCall* conf = FindCall(msg.get("conferenceId").to_str());
+
+ if (!conf || !conf->conference) {
+ LOG_ERR("GetParticipants: unable to find the conference call.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("GetParticipants: " << conf->id);
+ picojson::value::array array;
+ for (int i = 0; i < conf->participants.size(); i++) {
+ TelephonyCall* call = conf->participants[i];
+ if (call)
+ array.push_back(picojson::value(call->id));
+ }
+
+ instance_->SendSuccessReply(msg, picojson::value(array));
+}
+
+void TelephonyBackend::SplitCall(const picojson::value& msg) {
+ std::string cid = msg.get("callId").to_str();
+ TelephonyCall* call = FindCall(cid);
+
+ if (!call) {
+ LOG_ERR("Split: invalid call id " << cid);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (!call->conference) {
+ LOG_ERR("Split: not in a multiparty call.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ TelephonyCall* conf = call->conference;
+ if (!conf) {
+ LOG_ERR("Split: invalid conference id.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("Split: " << call->id << " from: " << conf->id);
+ GError* err = nullptr;
+ std::string path = IdToDbus(call->service->id);
+ GVariant* res = SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager,
+ "PrivateChat", g_variant_new("s", call->id.c_str()), &err);
+
+ if (err) {
+ LOG_ERR("Split: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ // oFono will notify the state changes to each (now held) participating call
+ // oFono returns the new array with the rest of calls, ignore it
+ RemoveParticipant(conf, call);
+ instance_->SendSuccessReply(msg);
+ g_variant_unref(res);
+}
+
+void TelephonyBackend::RemoveParticipant(TelephonyCall* conf,
+ TelephonyCall* call) {
+ if (!conf || !call)
+ return;
+
+ int size = conf->participants.size();
+ for (auto iter = conf->participants.begin();
+ iter != conf->participants.end();) {
+ if ((*iter)->id == call->id) { // don't delete, just erase
+ iter = conf->participants.erase(iter);
+ break;
+ } else {
+ ++iter;
+ }
+ }
+
+ // If there have been only 2 calls in the conference before the split,
+ // then remove the conf call object since we'll have two normal calls.
+ if (size == 2) {
+ RemoveCall(conf);
+ }
+}
+
+void TelephonyBackend::SendTones(const picojson::value& msg) {
+ // serviceId, tones
+ std::string sid = msg.get("serviceId").to_str();
+ TelephonyService* service = !sid.empty() ? FindService(sid) : nullptr;
+
+ if (!service && !default_service_) {
+ LOG_ERR("SendTones: unable to find telephony service.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ service = default_service_;
+
+ std::string tones = msg.get("tones").to_str();
+ if (tones.empty()) {
+ LOG_ERR("SendTones: empty sequence.");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ LOG_DBG("SendTones: " << tones << " to: " << sid);
+ GError* err = nullptr;
+ std::string path = IdToDbus(default_service_->id);
+ SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager, "SendTones",
+ g_variant_new("(s)", tones.c_str()), &err);
+
+ if (err) {
+ LOG_ERR("SendTones: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ instance_->SendSuccessReply(msg);
+}
+
+void TelephonyBackend::StartTone(const picojson::value& msg) {
+ instance_->SendErrorReply(msg, NOT_SUPPORTED_ERR);
+}
+
+void TelephonyBackend::StopTone(const picojson::value& msg) {
+ instance_->SendErrorReply(msg, NOT_SUPPORTED_ERR);
+}
+
+void TelephonyBackend::GetEmergencyNumbers(const picojson::value& msg) {
+ if (!default_service_ && (!QueryServices() || !default_service_)) {
+ LOG_ERR("GetEmergencyNumbers: no service");
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ GError* err = nullptr;
+ std::string path = IdToDbus(default_service_->id);
+ GVariant* res = SyncDBusCall(path.c_str(), kDbusOfonoVoiceCallManager,
+ "GetProperties", nullptr, &err);
+
+ if (err) {
+ LOG_ERR("GetEmergencyNumbers: " << err->message);
+ instance_->SendErrorReply(msg, NO_MODIFICATION_ALLOWED_ERR);
+ g_error_free(err);
+ return;
+ }
+
+ if (!res) {
+ LOG_ERR("Results from 'GetEmergencyNumbers' method is nullptr");
+ instance_->SendSuccessReply(msg, picojson::value(""));
+ return;
+ }
+
+ GVariantIter* prop_iter = nullptr;
+ g_variant_get(res, "(a{sv})", &prop_iter);
+
+ gchar* key = nullptr;
+ GVariant* value = nullptr;
+ picojson::value::array results;
+ while (g_variant_iter_next(prop_iter, "{sv}", &key, &value)) {
+ if (!strcmp(key, "EmergencyNumbers")) {
+ gchar* number = nullptr;
+ GVariantIter* num_iter = nullptr;
+ g_variant_get(value, "as", &num_iter);
+ while (g_variant_iter_loop(num_iter, "s", &number)) {
+ results.push_back(picojson::value(number));
+ }
+ g_variant_iter_free(num_iter);
+ g_variant_unref(value);
+ }
+ }
+
+ instance_->SendSuccessReply(msg, picojson::value(results));
+ g_variant_iter_free(prop_iter);
+ g_variant_unref(res);
+}
+
+void TelephonyBackend::EmergencyDial(const picojson::value& msg) {
+ // with ofono, need to make a regular call with the emergency number
+ instance_->SendErrorReply(msg, NOT_SUPPORTED_ERR);
+}
+
+bool TelephonyBackend::CheckRemoteParty(const std::string& address) {
+ // using the regexp proposed in oFono ./doc/voicecallmanager-api.txt
+ // but it doesn't work with g++; leaving here for reference
+ // std::regex phoneNumberRegex("[+]?[0-9*#]{1,80}");
+ // return std::regex_match(address, phoneNumberRegex);
+ char* p = const_cast<char*>(address.c_str());
+ int len = 80;
+ if (!p || !*p)
+ return false;
+ if (*p == '+')
+ p++;
+ for (; *p && --len; p++) {
+ if ((*p < '0' || *p > '9') && *p != '*' && *p != '#')
+ return false;
+ }
+ return (len > 0);
+}
+
+std::string TelephonyBackend::IdFromDbus(const char* id) {
+ std::string res(id);
+ for (int i = 0; i < res.size(); i++) {
+ if (res[i] == '/')
+ res[i] = '|';
+ }
+ return res;
+}
+
+std::string TelephonyBackend::IdToDbus(std::string& id) {
+ std::string res = id;
+ for (int i = 0; i < res.size(); i++) {
+ if (res[i] == '|')
+ res[i] = '/';
+ }
+ return res;
+}
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TELEPHONY_TELEPHONY_BACKEND_OFONO_H_
+#define TELEPHONY_TELEPHONY_BACKEND_OFONO_H_
+
+#include <gio/gio.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "common/extension.h"
+#include "common/picojson.h"
+#include "common/utils.h"
+#include "telephony/telephony_instance.h"
+#include "tizen/tizen.h"
+
+#define DECL_DBUS_SIGNAL_HANDLER(name) \
+ static void name(GDBusConnection* connection, \
+ const gchar* sender_name, \
+ const gchar* object_path, \
+ const gchar* interface_name, \
+ const gchar* signal_name, \
+ GVariant* parameters, \
+ gpointer user_data)
+
+#define DECL_SIGNAL_HANDLER(name) \
+ void name(const gchar* object_path, GVariant* parameters)
+
+#define _TEL_DBUS_ASYNC 0 // not much gain for async mode in a worker process
+
+#if _TEL_DBUS_ASYNC
+ #define DECL_API_METHOD(name) \
+ void name(const picojson::value& msg); \
+ static void name ## Finished(GObject* source_object, \
+ GAsyncResult* res, \
+ gpointer user_data)
+#else
+ #define DECL_API_METHOD(name) \
+ void name(const picojson::value& msg)
+#endif
+
+/*
+ * Implementation notes.
+ *
+ * Telephony services map to ofono Modem objects (plus subscriber identities).
+ * The type of Modem objects is "hfp" for phones connected via Bluetooth HFP,
+ * and "hardware" for internal modem.
+ *
+ * This extension identifies services/calls by modem/call DBUS object path.
+ * The JS shim would need to generate an opaque id out of that and maintain 1:1
+ * association, i.e. avoid having service objects with different serviceId but
+ * referring to the same modem path. This implementation exposes the object
+ * paths, which in turn exposes Bluetooth addresses to the
+ * client in case of HFP connection. So telephony is in the same privacy class
+ * as Bluetooth functionality, from fingerprinting point of view.
+ *
+ * Since oFono handles conference calls as arrays of call objects, with no
+ * abstraction for a dedicated conference call object, this implementation
+ * emulates it, and manages call id's for conference calls in a separate
+ * namespace, i.e. they don't correspond to call object paths. Also,
+ * oFono keeps all participating calls in 'active' state and updates the
+ * 'Multiparty' property. When participating calls are disconnected until only
+ * 2 calls remain, the conference call may or may not ne downgraded to normal
+ * calls. This implementation will handle them as separate normal calls and
+ * remove the conference call object in this case. When oFono changes one
+ * participating call's state, the conf call object is checked/updated.
+ * This is one issue complicating the implementation.
+ * The other is managing "the active call", which from oFono perspective,
+ * is not defined. The telephony protocols define which call is active, oFono
+ * just follows up the states. The W3C spec requires the notion of active call,
+ * as the one connected to audio resources.
+ * oFono doesn't provide that information. In this implementation, the active
+ * call is the last (normal or conference) call which became active.
+ *
+ * Since oFono does not handle multiple modems simultaneously, only one modem
+ * is kept powered on at any given time, so API calls using a different service
+ * than the default one will first try to power up the other, corresponding
+ * modem, and then switch back to the default.
+ */
+
+class TelephonyBackend;
+class TelephonyService;
+
+struct TelephonyCall {
+ explicit TelephonyCall(TelephonyService* s = NULL)
+ : service(s), conference(NULL), duration(0) {}
+ ~TelephonyCall() {}
+ TelephonyService* service; // the service owning the call
+ std::string id; // call object path or conference call id
+ std::string remote_party; // same as line_id in oFono (CLIP/COLP)
+ std::string state;
+ std::string state_reason; // mainly for disconnect reason
+ std::string saved_state; // to maintain state during conf call
+ std::string name;
+ std::string start_time;
+ double duration; // computed, in milliseconds
+ bool emergency;
+ std::string protocol; // duplicated from TelephonyService
+ TelephonyCall* conference; // the conference this call is part of
+ std::vector<TelephonyCall*> participants;
+ // other information from oFono is not handled in this implementation
+};
+
+struct TelephonyService {
+ std::string id; // modem object path
+ std::string name; // displayable name
+ std::string type; // "hfp" or "hw"
+ std::string model;
+ std::string revision;
+ std::string serial;
+ std::string protocol; // "gsm" or "cdma"
+ std::string provider;
+ bool emergency;
+ bool powered;
+ bool online;
+ bool lockdown;
+ // for future: std::vector<TelephonyCall*> calls;
+};
+
+class TelephonyBackend {
+ public:
+ explicit TelephonyBackend(TelephonyInstance* instance);
+ ~TelephonyBackend();
+
+ // implementing the API methods
+ DECL_API_METHOD(GetServices);
+ DECL_API_METHOD(SetServiceEnabled);
+ DECL_API_METHOD(SetDefaultService);
+ DECL_API_METHOD(GetDefaultService);
+ DECL_API_METHOD(GetCalls);
+ DECL_API_METHOD(DialCall);
+ DECL_API_METHOD(DeflectCall);
+ DECL_API_METHOD(AcceptCall);
+ DECL_API_METHOD(DisconnectCall);
+ DECL_API_METHOD(HoldCall);
+ DECL_API_METHOD(ResumeCall);
+ DECL_API_METHOD(TransferCall);
+ DECL_API_METHOD(CreateConference);
+ DECL_API_METHOD(GetConferenceParticipants);
+ DECL_API_METHOD(SplitCall);
+ DECL_API_METHOD(SendTones);
+ DECL_API_METHOD(StartTone);
+ DECL_API_METHOD(StopTone);
+ DECL_API_METHOD(GetEmergencyNumbers);
+ DECL_API_METHOD(EmergencyDial);
+
+ bool HangupCall(std::string& call_id);
+ void HangupAllCalls(std::string& service_id);
+
+ GVariant* SyncDBusCall(const gchar* object, const gchar* interface,
+ const gchar* method, GVariant* parameters, GError **error);
+
+ void AsyncDBusCall(const gchar* object, const gchar* interface,
+ const gchar* method, GVariant* parameters, GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ void SetDBusSignalHandler(const gchar* iface, const gchar* object,
+ const gchar* name, GDBusSignalCallback cb, void* user_data);
+
+ void EnableSignalHandlers();
+ void DisableSignalHandlers();
+
+ private:
+ // handling DBUS signals from oFono
+ DECL_DBUS_SIGNAL_HANDLER(SignalHandler);
+ DECL_SIGNAL_HANDLER(OnModemAdded);
+ DECL_SIGNAL_HANDLER(OnModemRemoved);
+ DECL_SIGNAL_HANDLER(OnModemChanged);
+ DECL_SIGNAL_HANDLER(OnCallAdded);
+ DECL_SIGNAL_HANDLER(OnCallRemoved);
+ DECL_SIGNAL_HANDLER(OnCallManagerChanged);
+ DECL_SIGNAL_HANDLER(OnCallChanged);
+ DECL_SIGNAL_HANDLER(OnCallDisconnectReason);
+
+ void NotifyDefaultServiceChanged();
+ void NotifyServiceAdded(TelephonyService* service);
+ void NotifyServiceChanged(const char* key, TelephonyService* service);
+ void NotifyServiceRemoved(TelephonyService* service);
+ void NotifyCallAdded(TelephonyCall* call);
+ void NotifyCallChanged(const char* key, TelephonyCall* call);
+ void NotifyCallRemoved(TelephonyCall* call);
+
+ bool QueryServices();
+ void RemoveService(TelephonyService* service);
+ bool SetModemEnabled(TelephonyService* service, bool on);
+ void RemoveCall(TelephonyCall* call);
+ bool CheckRemoteParty(const std::string& address);
+ void RemoveParticipant(TelephonyCall* conf, TelephonyCall* call);
+
+ std::string IdFromDbus(const char* id);
+ std::string IdToDbus(std::string& id);
+
+ TelephonyService* FindService(const std::string& id);
+ TelephonyCall* FindCall(const std::string& id);
+
+ picojson::object& ToJson(TelephonyCall* call, picojson::object& js);
+ picojson::object& ToJson(TelephonyService* s, picojson::object& js);
+
+ const char* UpdateCallProperty(TelephonyCall* call, const char* key,
+ GVariant* value);
+ const char* UpdateCallState(TelephonyCall* call, std::string& state);
+ const char* UpdateServiceProperty(TelephonyService* s, const char* key,
+ GVariant* value);
+ bool InitCall(TelephonyCall* call, const std::string& path,
+ GVariantIter* props);
+ bool InitService(TelephonyService* s, const std::string& path,
+ GVariantIter* props);
+ void UpdateCall(TelephonyCall* call);
+ void UpdateService(TelephonyService* s);
+ void UpdateDuration(TelephonyCall* call);
+ void LogCall(TelephonyCall* call);
+ void LogService(TelephonyService* s);
+ void LogCalls();
+ void LogServices();
+
+ void CheckActiveCall(TelephonyCall* call = NULL);
+
+ private:
+ TelephonyInstance* instance_;
+ std::vector<guint> dbus_listeners_;
+ std::vector<TelephonyService*> services_;
+ TelephonyService* default_service_;
+ TelephonyCall* active_call_; // the one which has audio
+ std::vector<TelephonyCall*> calls_;
+ std::vector<TelephonyCall*> removed_calls_;
+ GCancellable* cancellable_;
+
+ DISALLOW_COPY_AND_ASSIGN(TelephonyBackend);
+};
+
+#endif // TELEPHONY_TELEPHONY_BACKEND_OFONO_H_
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "telephony/telephony_extension.h"
+
+#include <glib-object.h>
+#include "telephony/telephony_instance.h"
+
+common::Extension* CreateExtension() {
+ return new TelephonyExtension;
+}
+
+extern const char kSource_telephony_api[];
+
+TelephonyExtension::TelephonyExtension() {
+ SetExtensionName("tizen.telephony");
+ SetJavaScriptAPI(kSource_telephony_api);
+}
+
+TelephonyExtension::~TelephonyExtension() {}
+
+common::Instance* TelephonyExtension::CreateInstance() {
+ return new TelephonyInstance;
+}
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TELEPHONY_TELEPHONY_EXTENSION_H_
+#define TELEPHONY_TELEPHONY_EXTENSION_H_
+
+#include "common/extension.h"
+
+class TelephonyExtension : public common::Extension {
+ public:
+ TelephonyExtension();
+ virtual ~TelephonyExtension();
+
+ private:
+ // common::Extension implementation.
+ virtual common::Instance* CreateInstance();
+};
+
+#endif // TELEPHONY_TELEPHONY_EXTENSION_H_
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "telephony/telephony_instance.h"
+
+#include <string>
+
+#include "common/picojson.h"
+#include "telephony/telephony_backend_ofono.h"
+#include "tizen/tizen.h"
+
+namespace {
+
+const char kCmdGetServices[] = "getServices";
+const char kCmdSetDefaultService[] = "setDefaultService";
+const char kCmdSetServiceEnabled[] = "setServiceEnabled";
+const char kCmdGetCalls[] = "getCalls";
+const char kCmdDial[] = "dial";
+const char kCmdAccept[] = "accept";
+const char kCmdDisconnect[] = "disconnect";
+const char kCmdHold[] = "hold";
+const char kCmdResume[] = "resume";
+const char kCmdDeflect[] = "deflect";
+const char kCmdTransfer[] = "transfer";
+const char kCmdSplit[] = "split";
+const char kCmdSendTones[] = "sendTones";
+const char kCmdStartTone[] = "startTone";
+const char kCmdStopTone[] = "stopTone";
+const char kCmdGetEmergencyNumbers[] = "getEmergencyNumbers";
+const char kCmdEmergencyDial[] = "emergencyDial";
+const char kCmdCreateConference[] = "createConference";
+const char kCmdGetParticipants[] = "getParticipants";
+const char kCmdEnableNotifications[] = "enableNotifications";
+const char kCmdDisableNotifications[] = "disableNotifications";
+
+} // namespace
+
+TelephonyInstance::TelephonyInstance() : backend_(new TelephonyBackend(this)) {
+}
+
+TelephonyInstance::~TelephonyInstance() {
+ delete backend_;
+}
+
+void TelephonyInstance::HandleMessage(const char* msg) {
+ picojson::value v;
+ std::string err;
+ picojson::parse(v, msg, msg + strlen(msg), &err);
+ if (!err.empty()) {
+ std::cerr << "Error: ignoring empty message.\n";
+ return;
+ }
+
+ if (!backend_) {
+ SendErrorReply(v, NO_MODIFICATION_ALLOWED_ERR,
+ "Telephony backend not initialized.");
+ return;
+ }
+
+ std::string cmd = v.get("cmd").to_str();
+ if (cmd == kCmdGetServices) {
+ backend_->GetServices(v);
+ } else if (cmd == kCmdSetDefaultService) {
+ backend_->SetDefaultService(v);
+ } else if (cmd == kCmdSetServiceEnabled) {
+ backend_->SetServiceEnabled(v);
+ } else if (cmd == kCmdEnableNotifications) {
+ backend_->EnableSignalHandlers();
+ } else if (cmd == kCmdDisableNotifications) {
+ backend_->DisableSignalHandlers();
+ } else if (cmd == kCmdGetCalls) {
+ backend_->GetCalls(v);
+ } else if (cmd == kCmdDial) {
+ backend_->DialCall(v);
+ } else if (cmd == kCmdAccept) {
+ backend_->AcceptCall(v);
+ } else if (cmd == kCmdDisconnect) {
+ backend_->DisconnectCall(v);
+ } else if (cmd == kCmdHold) {
+ backend_->HoldCall(v);
+ } else if (cmd == kCmdResume) {
+ backend_->ResumeCall(v);
+ } else if (cmd == kCmdDeflect) {
+ backend_->DeflectCall(v);
+ } else if (cmd == kCmdTransfer) {
+ backend_->TransferCall(v);
+ } else if (cmd == kCmdSendTones) {
+ backend_->SendTones(v);
+ } else if (cmd == kCmdStartTone) {
+ backend_->StartTone(v);
+ } else if (cmd == kCmdStopTone) {
+ backend_->StopTone(v);
+ } else if (cmd == kCmdGetEmergencyNumbers) {
+ backend_->GetEmergencyNumbers(v);
+ } else if (cmd == kCmdEmergencyDial) {
+ backend_->EmergencyDial(v);
+ } else if (cmd == kCmdCreateConference) {
+ backend_->CreateConference(v);
+ } else if (cmd == kCmdGetParticipants) {
+ backend_->GetConferenceParticipants(v);
+ } else if (cmd == kCmdSplit) {
+ backend_->SplitCall(v);
+ } else {
+ std::cout << "Ignoring unknown command: " << cmd;
+ }
+}
+
+void TelephonyInstance::HandleSyncMessage(const char* message) {
+}
+
+void TelephonyInstance::SendErrorReply(const picojson::value& msg,
+ const int error_code, const char* error_msg) {
+ picojson::value::object reply;
+ reply["promiseId"] = msg.get("promiseId");
+ reply["cmd"] = msg.get("cmd");
+ reply["isError"] = picojson::value(true);
+ reply["errorCode"] = picojson::value(static_cast<double>(error_code));
+ reply["errorMessage"] = error_msg ? picojson::value(error_msg) :
+ msg.get("cmd");
+ picojson::value v(reply);
+ PostMessage(v.serialize().c_str());
+}
+
+void TelephonyInstance::SendSuccessReply(const picojson::value& msg,
+ const picojson::value& value) {
+ picojson::value::object reply;
+ reply["promiseId"] = msg.get("promiseId");
+ reply["cmd"] = msg.get("cmd");
+ reply["isError"] = picojson::value(false);
+ reply["returnValue"] = value;
+ picojson::value v(reply);
+ PostMessage(v.serialize().c_str());
+}
+
+void TelephonyInstance::SendSuccessReply(const picojson::value& msg) {
+ picojson::value::object reply;
+ reply["promiseId"] = msg.get("promiseId");
+ reply["cmd"] = msg.get("cmd");
+ reply["isError"] = picojson::value(false);
+ picojson::value v(reply);
+ PostMessage(v.serialize().c_str());
+}
+
+void TelephonyInstance::SendNotification(const picojson::value& msg) {
+ PostMessage(msg.serialize().c_str());
+}
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TELEPHONY_TELEPHONY_INSTANCE_H_
+#define TELEPHONY_TELEPHONY_INSTANCE_H_
+
+#include <glib.h>
+#include <thread> // NOLINT
+
+#include "common/extension.h"
+
+namespace picojson {
+
+class value;
+
+}
+
+class TelephonyBackend;
+
+class TelephonyInstance : public common::Instance {
+ friend class TelephonyBackend;
+ public:
+ TelephonyInstance();
+ virtual ~TelephonyInstance();
+
+ private:
+ // common::Instance implementation.
+ virtual void HandleMessage(const char* msg);
+ virtual void HandleSyncMessage(const char* msg);
+
+ void SendSuccessReply(const picojson::value& msg);
+ void SendSuccessReply(const picojson::value& msg,
+ const picojson::value& value);
+ void SendErrorReply(const picojson::value& msg,
+ const int error_code, const char* error_msg = NULL);
+ void SendNotification(const picojson::value& msg);
+
+ private:
+ std::thread dbus_thread_;
+ GMainLoop* dbus_loop_;
+ TelephonyBackend* backend_;
+};
+
+#endif // TELEPHONY_TELEPHONY_INSTANCE_H_
--- /dev/null
+// Copyright (c) 2014 Intel Corporation. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TELEPHONY_TELEPHONY_LOGGING_H_
+#define TELEPHONY_TELEPHONY_LOGGING_H_
+
+#define LOG_ERR(x) do { std::cout << "[Error] " << x << std::endl; } while (0)
+
+#ifdef NDEBUG
+ #define LOG_DBG(x) do {} while (0)
+#else
+ #define LOG_DBG(x) do { std::cout << "[DBG] " << x << std::endl; } while (0)
+#endif
+
+#endif // TELEPHONY_TELEPHONY_LOGGING_H_
[ 'extension_host_os == "ivi"', {
'dependencies': [
'audiosystem/audiosystem.gyp:*',
- 'vehicle/vehicle.gyp:*',
'sso/sso.gyp:*',
+ 'telephony/telephony.gyp:*',
+ 'vehicle/vehicle.gyp:*',
],
}],
],