[Phone] Added IVI Phone support
authorJimmy Huang <jimmy.huang@intel.com>
Tue, 6 May 2014 17:37:24 +0000 (10:37 -0700)
committerJimmy Huang <jimmy.huang@intel.com>
Fri, 13 Jun 2014 19:35:17 +0000 (12:35 -0700)
This extension implements the phone interfaces for IVI using phoned.
It replaces the existing wrt-plugins-ivi-hfp used by Modello home screen.

Original wrt plugin
https://review.tizen.org/git/?p=profile/ivi/wrt-plugins-ivi-hfp.git

It implements the following api:

- selectRemoteDevice()
- unselectRemoteDevice()
- getSelectedRemoteDevice()
- invokeCall()
- answerCall()
- hangupCall()
- activeCall()
- muteCall()
- getContacts()
- getCallHistory()
- addRemoteDeviceSelectedListener()
- removeRemoteDeviceSelectedListener()
- addCallChangedListener()
- removeCallChangedListener()
- addCallHistoryEntryAddedListener()
- removeCallHistoryEntryAddedListener()
- addCallHistoryChangedListener()
- removeCallHistoryChangedListener()
- addContactsChangedListener()
- removeContactsChangedListener()

BUG=XWALK-1661

Signed-off-by: Jimmy Huang <jimmy.huang@intel.com>
phone/phone.gyp [new file with mode: 0644]
phone/phone_api.js [new file with mode: 0644]
phone/phone_extension.cc [new file with mode: 0644]
phone/phone_extension.h [new file with mode: 0644]
phone/phone_instance.cc [new file with mode: 0644]
phone/phone_instance.h [new file with mode: 0644]
tizen-wrt.gyp

diff --git a/phone/phone.gyp b/phone/phone.gyp
new file mode 100644 (file)
index 0000000..00aad1a
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  'includes':[
+    '../common/common.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'tizen_phone',
+      'type': 'loadable_module',
+      'variables': {
+        'packages': [
+          'gio-2.0',
+        ],
+      },
+      'includes': [
+        '../common/pkg-config.gypi',
+      ],
+      'sources': [
+        'phone_api.js',
+        'phone_extension.cc',
+        'phone_extension.h',
+        'phone_instance.cc',
+        'phone_instance.h',
+      ],
+    },
+  ],
+}
diff --git a/phone/phone_api.js b/phone/phone_api.js
new file mode 100644 (file)
index 0000000..5210efb
--- /dev/null
@@ -0,0 +1,542 @@
+// 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.
+
+var _callbacks = {};
+var _nextReplyId = 0;
+
+var _selected_listeners = {};
+var _selected_listener_id = 0;
+var _selected_listeners_count = 0;
+
+var _call_changed_listeners = {};
+var _call_changed_listener_id = 0;
+var _call_changed_listeners_count = 0;
+
+var _call_history_added_listeners = {};
+var _call_history_added_listener_id = 0;
+var _call_history_added_listeners_count = 0;
+
+var _call_history_changed_listeners = {};
+var _call_history_changed_listener_id = 0;
+var _call_history_changed_listeners_count = 0;
+
+var _contacts_changed_listeners = {};
+var _contacts_changed_listener_id = 0;
+var _contacts_changed_listeners_count = 0;
+
+function PhoneError(code, message) {
+  Object.defineProperties(this, {
+    'code': { writable: false, value: uri, enumerable: true },
+    'message': { writable: false, value: id, enumerable: true }
+  });
+}
+
+function ActiveCall(json) {
+  for (var field in json) {
+    var val = json[field];
+    Object.defineProperty(this, field, { writable: false, value: val, enumerable: true });
+  }
+}
+
+function getNextReplyId() {
+  return _nextReplyId++;
+}
+
+function postMessage(msg, callback) {
+  var replyId = getNextReplyId();
+  _callbacks[replyId] = callback;
+  msg.replyId = replyId;
+  extension.postMessage(JSON.stringify(msg));
+}
+
+var sendSyncMessage = function(msg) {
+  return JSON.parse(extension.internal.sendSyncMessage(JSON.stringify(msg)));
+};
+
+extension.setMessageListener(function(msg) {
+  var m = JSON.parse(msg);
+  var replyId = m.replyId;
+  var callback = _callbacks[replyId];
+
+  if (m.cmd === 'signal') {
+    if (!m.signal_name) {
+      console.error('Invalid signal from Phone api');
+      return;
+    }
+
+    if (m.signal_name === 'RemoteDeviceSelected') {
+      handleRemoteDeviceSelectedSignal(m);
+    } else if (m.signal_name === 'CallChanged') {
+      handleCallChangedSignal(m);
+    } else if (m.signal_name === 'CallHistoryEntryAdded') {
+      handleCallHistoryEntryAddedSignal(m);
+    } else if (m.signal_name === 'CallHistoryChanged') {
+      handleCallHistoryChangedSignal(m);
+    } else if (m.signal_name === 'ContactsChanged') {
+      handleContactsChangedSignal(m);
+    }
+  } else if (!isNaN(parseInt(replyId)) && (typeof(callback) === 'function')) {
+    callback(m);
+    delete m.replyId;
+    delete _callbacks[replyId];
+  } else {
+    console.error('Invalid replyId from Phone api: ' + replyId);
+  }
+});
+
+function handleRemoteDeviceSelectedSignal(msg) {
+  for (var key in _selected_listeners) {
+    var cb = _selected_listeners[key];
+    if (!cb || typeof(cb) !== 'function') {
+      console.error('No listener object found for id ' + key);
+      throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
+    }
+    cb(msg.signal_value);
+  }
+}
+
+function handleCallChangedSignal(msg) {
+  for (var key in _call_changed_listeners) {
+    var cb = _call_changed_listeners[key];
+    if (!cb || typeof(cb) !== 'function') {
+      console.error('No listener object found for id ' + key);
+      throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
+    }
+    cb(msg.signal_value);
+  }
+}
+
+function handleCallHistoryEntryAddedSignal(msg) {
+  for (var key in _call_history_added_listeners) {
+    var cb = _call_history_added_listeners[key];
+    if (!cb || typeof(cb) !== 'function') {
+      console.error('No listener object found for id ' + key);
+      throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
+    }
+    cb(msg.signal_value);
+  }
+}
+
+function handleCallHistoryChangedSignal(msg) {
+  for (var key in _call_history_changed_listeners) {
+    var cb = _call_history_changed_listeners[key];
+    if (!cb || typeof(cb) !== 'function') {
+      console.error('No listener object found for id ' + key);
+      throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
+    }
+    cb();
+  }
+}
+
+function handleContactsChangedSignal(msg) {
+  for (var key in _contacts_changed_listeners) {
+    var cb = _contacts_changed_listeners[key];
+    if (!cb || typeof(cb) !== 'function') {
+      console.error('No listener object found for id ' + key);
+      throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
+    }
+    cb();
+  }
+}
+
+exports.selectRemoteDevice = function(address) {
+  if (typeof address !== 'string' && address != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var msg = { cmd: 'SelectRemoteDevice', address: address };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('SelectedRemoteDevice failed');
+      throw new tizen.WebAPIException(result.errorCode);
+    }
+  });
+};
+
+exports.unselectRemoteDevice = function() {
+  var msg = { cmd: 'UnselectRemoteDevice' };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('UnselectRemoteDevice failed');
+      throw new tizen.WebAPIException(result.errorCode);
+    }
+  });
+};
+
+exports.getSelectedRemoteDevice = function(successCallback) {
+  var msg = { cmd: 'GetSelectedRemoteDevice' };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('GetSelectedRemoteDevice failed');
+      throw new tizen.WebAPIException(result.errorCode);
+    }
+
+    if (successCallback && result.value != undefined) {
+      successCallback(result.value);
+    }
+  });
+};
+
+exports.invokeCall = function(phoneNumber, errorCallback) {
+  if (typeof phoneNumber !== 'string' && phoneNumber != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var msg = { cmd: 'InvokeCall', phoneNumber: phoneNumber };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('InvokeCall failed');
+      if (errorCallback) {
+        var error = { message: 'InvokeCall failed' };
+        if (result.errorMessage)
+          error.message += ', error: ' + result.errorMessage;
+        errorCallback(error);
+      }
+    }
+  });
+};
+
+exports.answerCall = function(errorCallback) {
+  var msg = { cmd: 'AnswerCall' };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('AnswerCall failed');
+      if (errorCallback) {
+        var error = { message: 'AnswerCall failed' };
+        if (result.errorMessage)
+          error.message += ', error: ' + result.errorMessage;
+        errorCallback(error);
+      }
+    }
+  });
+};
+
+exports.hangupCall = function(errorCallback) {
+  var msg = { cmd: 'HangupCall' };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('HangupCall failed');
+      if (errorCallback) {
+        var error = { message: 'HangupCall failed' };
+        if (result.errorMessage)
+          error.message += ', error: ' + result.errorMessage;
+        errorCallback(error);
+      }
+    }
+  });
+};
+
+exports.activeCall = function() {
+  var result = sendSyncMessage({ cmd: 'ActiveCall' });
+  if (result.isError) {
+    console.error('activeCall failed');
+    throw new tizen.WebAPIException(result.errorCode);
+  }
+
+  if (result.value != undefined) {
+    return new ActiveCall(result.value);
+  }
+
+  return null;
+};
+
+exports.muteCall = function(mute, errorCallback) {
+  if (typeof mute !== 'boolean' && mute != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var msg = { cmd: 'MuteCall', mute: mute };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('MuteCall failed');
+      if (errorCallback) {
+        var error = { message: 'MuteCall failed' };
+        if (result.errorMessage)
+          error.message += ', error: ' + result.errorMessage;
+        errorCallback(error);
+      }
+    }
+  });
+};
+
+exports.getContacts = function(count, successCallback, errorCallback) {
+  if (typeof count !== 'number' && count != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var msg = { cmd: 'GetContacts', count: count };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('GetContacts failed');
+      if (errorCallback) {
+        var error = { message: 'GetContacts failed' };
+        if (result.errorMessage)
+          error.message += ', error: ' + result.errorMessage;
+        errorCallback(error);
+      }
+    }
+
+    if (successCallback && result.value != undefined) {
+      successCallback(result.value);
+    }
+  });
+};
+
+exports.getCallHistory = function(count, successCallback, errorCallback) {
+  if (typeof count !== 'number' && count != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var msg = { cmd: 'GetCallHistory', count: count };
+  postMessage(msg, function(result) {
+    if (result.isError) {
+      console.error('GetCallHistory failed');
+      if (errorCallback) {
+        var error = { message: 'GetCallHistory failed' };
+        if (result.errorMessage)
+          error.message += ', error: ' + result.errorMessage;
+        errorCallback(error);
+      }
+    }
+
+    if (successCallback && result.value != undefined) {
+      successCallback(result.value);
+    }
+  });
+};
+
+exports.addRemoteDeviceSelectedListener = function(listener) {
+  if (!(listener instanceof Function) && listener != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  for (var key in _selected_listeners) {
+    if (_selected_listeners[key] == listener) {
+      console.log('same listener added');
+      return key;
+    }
+  }
+
+  _selected_listeners[++_selected_listener_id] = listener;
+  _selected_listeners_count++;
+  if (_selected_listeners_count == 1) {
+    var msg = { cmd: 'AddRemoteDeviceSelectedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('AddRemoteDeviceSelectedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+
+  return _selected_listener_id;
+};
+
+exports.removeRemoteDeviceSelectedListener = function(listener_id) {
+  if (typeof listener_id !== 'number' && listener_id != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var listener = _selected_listeners[listener_id];
+  if (!listener) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
+  }
+
+  delete _selected_listeners[listener_id];
+  _selected_listeners_count--;
+  if (_selected_listeners_count == 0) {
+    var msg = { cmd: 'RemoveRemoteDeviceSelectedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('RemoveRemoteDeviceSelectedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+};
+
+exports.addCallChangedListener = function(listener) {
+  if (!(listener instanceof Function) && listener != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  for (var key in _call_changed_listeners) {
+    if (_call_changed_listeners[key] == listener) {
+      console.log('same listener added');
+      return key;
+    }
+  }
+
+  _call_changed_listeners[++_call_changed_listener_id] = listener;
+  _call_changed_listeners_count++;
+  if (_call_changed_listeners_count == 1) {
+    var msg = { cmd: 'AddCallChangedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('AddCallChangedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+
+  return _call_changed_listener_id;
+};
+
+exports.removeCallChangedListener = function(listener_id) {
+  if (typeof listener_id !== 'number' && listener_id != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var listener = _call_changed_listeners[listener_id];
+  if (!listener) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
+  }
+
+  delete _call_changed_listeners[listener_id];
+  _call_changed_listeners_count--;
+  if (_call_changed_listeners_count == 0) {
+    var msg = { cmd: 'RemoveCallChangedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('RemoveCallChangedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+};
+
+exports.addCallHistoryEntryAddedListener = function(listener) {
+  if (!(listener instanceof Function) && listener != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  for (var key in _call_history_added_listeners) {
+    if (_call_history_added_listeners[key] == listener) {
+      console.log('same listener added');
+      return key;
+    }
+  }
+
+  _call_history_added_listeners[++_call_history_added_listener_id] = listener;
+  _call_history_added_listeners_count++;
+  if (_call_history_added_listeners_count == 1) {
+    var msg = { cmd: 'AddCallHistoryEntryAddedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('AddCallHistoryEntryAddedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+
+  return _call_history_added_listener_id;
+};
+
+exports.removeCallHistoryEntryAddedListener = function(listener_id) {
+  if (typeof listener_id !== 'number' && listener_id != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var listener = _call_history_added_listeners[listener_id];
+  if (!listener) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
+  }
+
+  delete _call_history_added_listeners[listener_id];
+  _call_history_added_listeners_count--;
+  if (_call_history_added_listeners_count == 0) {
+    var msg = { cmd: 'RemoveCallHistoryEntryAddedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('RemoveCallHistoryEntryAddedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+};
+
+exports.addCallHistoryChangedListener = function(listener) {
+  if (!(listener instanceof Function) && listener != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  for (var key in _call_history_changed_listeners) {
+    if (_call_history_changed_listeners[key] == listener) {
+      console.log('same listener added');
+      return key;
+    }
+  }
+
+  _call_history_changed_listeners[++_call_history_changed_listener_id] = listener;
+  _call_history_changed_listeners_count++;
+  if (_call_history_changed_listeners_count == 1) {
+    var msg = { cmd: 'AddCallHistoryChangedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('AddCallHistoryChangedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+
+  return _call_history_changed_listener_id;
+};
+
+exports.removeCallHistoryChangedListener = function(listener_id) {
+  if (typeof listener_id !== 'number' && listener_id != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var listener = _call_history_changed_listeners[listener_id];
+  if (!listener) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
+  }
+
+  delete _call_history_changed_listeners[listener_id];
+  _call_history_changed_listeners_count--;
+  if (_call_history_changed_listeners_count == 0) {
+    var msg = { cmd: 'RemoveCallHistoryChangedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('RemoveCallHistoryChangedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+};
+
+exports.addContactsChangedListener = function(listener) {
+  if (!(listener instanceof Function) && listener != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  for (var key in _contacts_changed_listeners) {
+    if (_contacts_changed_listeners[key] == listener) {
+      console.log('same listener added');
+      return key;
+    }
+  }
+
+  _contacts_changed_listeners[++_contacts_changed_listener_id] = listener;
+  _contacts_changed_listeners_count++;
+  if (_contacts_changed_listeners_count == 1) {
+    var msg = { cmd: 'AddContactsChangedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('AddContactsChangedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+
+  return _contacts_changed_listener_id;
+};
+
+exports.removeContactsChangedListener = function(listener_id) {
+  if (typeof listener_id !== 'number' && listener_id != undefined)
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+
+  var listener = _contacts_changed_listeners[listener_id];
+  if (!listener) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
+  }
+
+  delete _contacts_changed_listeners[listener_id];
+  _contacts_changed_listeners_count--;
+  if (_contacts_changed_listeners_count == 0) {
+    var msg = { cmd: 'RemoveContactsChangedListener' };
+    postMessage(msg, function(result) {
+      if (result.isError) {
+        console.error('RemoveContactsChangedListener failed');
+        throw new tizen.WebAPIException(result.errorCode);
+      }
+    });
+  }
+};
diff --git a/phone/phone_extension.cc b/phone/phone_extension.cc
new file mode 100644 (file)
index 0000000..e32edc1
--- /dev/null
@@ -0,0 +1,24 @@
+// 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 "phone/phone_extension.h"
+#include "phone/phone_instance.h"
+
+common::Extension* CreateExtension() {
+  return new PhoneExtension();
+}
+
+// This will be generated from phone_api.js
+extern const char kSource_phone_api[];
+
+PhoneExtension::PhoneExtension() {
+  SetExtensionName("tizen.phone");
+  SetJavaScriptAPI(kSource_phone_api);
+}
+
+PhoneExtension::~PhoneExtension() {}
+
+common::Instance* PhoneExtension::CreateInstance() {
+  return new PhoneInstance;
+}
diff --git a/phone/phone_extension.h b/phone/phone_extension.h
new file mode 100644 (file)
index 0000000..f576264
--- /dev/null
@@ -0,0 +1,20 @@
+// 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 PHONE_PHONE_EXTENSION_H_
+#define PHONE_PHONE_EXTENSION_H_
+
+#include "common/extension.h"
+
+class PhoneExtension : public common::Extension {
+ public:
+  PhoneExtension();
+  virtual ~PhoneExtension();
+
+ private:
+  // common::Extension implementation
+  virtual common::Instance* CreateInstance();
+};
+
+#endif  // PHONE_PHONE_EXTENSION_H_
diff --git a/phone/phone_instance.cc b/phone/phone_instance.cc
new file mode 100644 (file)
index 0000000..baaf339
--- /dev/null
@@ -0,0 +1,515 @@
+// 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 "phone/phone_instance.h"
+
+#include <gio/gio.h>
+#include <string>
+
+namespace {
+
+const char kPhoneService[] = "org.tizen.phone";
+const char kPhoneInterface[] = "org.tizen.Phone";
+const char kPhoneObjectPath[] = "/";
+
+}  // namespace
+
+guint PhoneInstance::remote_device_selected_listener_id_ = 0;
+guint PhoneInstance::call_changed_listener_id_ = 0;
+guint PhoneInstance::call_history_entry_added_listener_id_ = 0;
+guint PhoneInstance::call_history_changed_listener_id_ = 0;
+guint PhoneInstance::contacts_changed_listener_id_ = 0;
+
+PhoneInstance::PhoneInstance()
+    : main_loop_(g_main_loop_new(0, FALSE)),
+      thread_(PhoneInstance::RunMainloop, this) {
+  thread_.detach();
+}
+
+PhoneInstance::~PhoneInstance() {
+  g_main_loop_quit(main_loop_);
+}
+
+void PhoneInstance::RunMainloop(void* data) {
+  PhoneInstance* self = reinterpret_cast<PhoneInstance*>(data);
+  GMainContext* ctx = g_main_context_default();
+  g_main_context_push_thread_default(ctx);
+  g_main_loop_run(self->main_loop_);
+  g_main_loop_unref(self->main_loop_);
+}
+
+void PhoneInstance::HandleMessage(const char* msg) {
+  picojson::value v;
+
+  std::string err;
+  picojson::parse(v, msg, msg + strlen(msg), &err);
+  if (!err.empty()) {
+    return;
+  }
+
+  const std::string cmd = v.get("cmd").to_str();
+  if (cmd == "SelectRemoteDevice") {
+    HandleSelectRemoteDevice(v);
+  } else if (cmd == "UnselectRemoteDevice") {
+    HandleUnselectRemoteDevice(v);
+  } else if (cmd == "InvokeCall") {
+    HandleInvokeCall(v);
+  } else if (cmd == "AnswerCall") {
+    HandleAnswerCall(v);
+  } else if (cmd == "HangupCall") {
+    HandleHangupCall(v);
+  } else if (cmd == "ActiveCall") {
+    HandleActiveCall(v);
+  } else if (cmd == "MuteCall") {
+    HandleMuteCall(v);
+  } else if (cmd == "GetSelectedRemoteDevice" ||
+             cmd == "GetContacts" ||
+             cmd == "GetCallHistory") {
+    HandleGet(cmd, v);
+  } else if (cmd == "AddRemoteDeviceSelectedListener") {
+    HandleAddListener(remote_device_selected_listener_id_,
+        std::string("RemoteDeviceSelected"), v);
+  } else if (cmd == "RemoveRemoteDeviceSelectedListener") {
+    HandleRemoveListener(remote_device_selected_listener_id_,
+        std::string("RemoteDeviceSelected"), v);
+  } else if (cmd == "AddCallChangedListener") {
+    HandleAddListener(call_changed_listener_id_,
+        std::string("CallChanged"), v);
+  } else if (cmd == "RemoveCallChangedListener") {
+    HandleRemoveListener(call_changed_listener_id_,
+        std::string("CallChanged"), v);
+  } else if (cmd == "AddCallHistoryEntryAddedListener") {
+    HandleAddListener(call_history_entry_added_listener_id_,
+        std::string("CallHistoryEntryAddedChanged"), v);
+  } else if (cmd == "RemoveCallHistoryEntryAddedListener") {
+    HandleRemoveListener(call_history_entry_added_listener_id_,
+        std::string("CallHistoryEntryAddedChanged"), v);
+  } else if (cmd == "AddCallHistoryChangedListener") {
+    HandleAddListener(call_history_changed_listener_id_,
+        std::string("CallHistoryChanged"), v);
+  } else if (cmd == "RemoveCallHistoryChangedListener") {
+    HandleRemoveListener(call_history_changed_listener_id_,
+        std::string("CallHistoryChanged"), v);
+  } else if (cmd == "AddContactsChangedListener") {
+    HandleAddListener(contacts_changed_listener_id_,
+        std::string("ContactsChanged"), v);
+  } else if (cmd == "RemoveContactsChangedListener") {
+    HandleRemoveListener(contacts_changed_listener_id_,
+        std::string("ContactsChanged"), v);
+  } else {
+    std::cerr << "Unknown command: " << cmd << "\n";
+  }
+}
+
+void PhoneInstance::HandleSyncMessage(const char* msg) {
+  picojson::value v;
+  std::string err;
+
+  picojson::parse(v, msg, msg + strlen(msg), &err);
+  if (!err.empty()) {
+    return;
+  }
+
+  const std::string cmd = v.get("cmd").to_str();
+  if (cmd == "ActiveCall") {
+    HandleActiveCall(v);
+  } else {
+    std::cerr << "ASSERT NOT REACHED.\n";
+  }
+}
+
+void PhoneInstance::SendSyncErrorReply(WebApiAPIErrors error_code,
+    const std::string& error_msg = "") {
+  picojson::value::object o;
+  o["isError"] = picojson::value(true);
+  o["errorCode"] = picojson::value(static_cast<double>(error_code));
+  if (!error_msg.empty())
+    o["errorMessage"] = picojson::value(error_msg.c_str());
+  picojson::value v(o);
+  SendSyncReply(v.serialize().c_str());
+}
+
+void PhoneInstance::SendSyncSuccessReply() {
+  picojson::value::object o;
+  o["isError"] = picojson::value(false);
+  picojson::value v(o);
+  SendSyncReply(v.serialize().c_str());
+}
+
+void PhoneInstance::SendSyncSuccessReply(const picojson::value& value) {
+  picojson::value::object o;
+  o["isError"] = picojson::value(false);
+  o["value"] = value;
+  picojson::value v(o);
+  SendSyncReply(v.serialize().c_str());
+}
+
+void PhoneInstance::PostAsyncReply(const picojson::value& msg,
+    picojson::value::object& reply) {
+  reply["replyId"] = picojson::value(msg.get("replyId").get<double>());
+  picojson::value v(reply);
+  PostMessage(v.serialize().c_str());
+}
+
+void PhoneInstance::PostAsyncErrorReply(const picojson::value& msg,
+    WebApiAPIErrors error_code, const std::string& error_msg = "") {
+  picojson::value::object reply;
+  reply["isError"] = picojson::value(true);
+  reply["errorCode"] = picojson::value(static_cast<double>(error_code));
+  if (!error_msg.empty())
+    reply["errorMessage"] = picojson::value(error_msg.c_str());
+  PostAsyncReply(msg, reply);
+}
+
+void PhoneInstance::PostAsyncSuccessReply(const picojson::value& msg,
+    const picojson::value& value) {
+  picojson::value::object reply;
+  reply["isError"] = picojson::value(false);
+  reply["value"] = value;
+  PostAsyncReply(msg, reply);
+}
+
+void PhoneInstance::PostAsyncSuccessReply(const picojson::value& msg) {
+  picojson::value::object reply;
+  reply["isError"] = picojson::value(false);
+  PostAsyncReply(msg, reply);
+}
+
+void PhoneInstance::SendSignal(const picojson::value& signal_name,
+    const picojson::value& signal_value) {
+  picojson::value::object o;
+  o["cmd"] = picojson::value("signal");
+  o["signal_name"] = signal_name;
+  o["signal_value"] = signal_value;
+  picojson::value msg(o);
+  PostMessage(msg.serialize().c_str());
+}
+
+GVariant* PhoneInstance::CallDBus(const gchar* method_name,
+    GVariant* parameters,
+    GError** error) {
+  if (!method_name)
+    return NULL;
+
+  return g_dbus_connection_call_sync(
+      g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+      kPhoneService,
+      kPhoneObjectPath,
+      kPhoneInterface,
+      method_name,
+      parameters,
+      NULL,
+      G_DBUS_CALL_FLAGS_NONE,
+      -1,
+      NULL,
+      error);
+}
+
+void PhoneInstance::HandleSignal(GDBusConnection* connection,
+    const gchar* sender_name,
+    const gchar* object_path,
+    const gchar* interface_name,
+    const gchar* signal_name,
+    GVariant* parameters,
+    gpointer user_data) {
+  PhoneInstance* instance = static_cast<PhoneInstance*>(user_data);
+  if (!instance) {
+    std::cerr << "Failed to cast to instance..." << "\n";
+    return;
+  }
+
+  if (!strcmp(signal_name, "RemoteDeviceSelected") ||
+      !strcmp(signal_name, "CallHistoryEntryAdded")) {
+    const gchar* result;
+    g_variant_get(parameters, "(s)", &result);
+    if (!result)
+      return;
+
+    picojson::value value;
+    std::string err;
+    picojson::parse(value, result, result + strlen(result), &err);
+    if (!err.empty()) {
+      std::cerr << "cannot parse result.\n";
+      return;
+    }
+
+    instance->SendSignal(picojson::value(signal_name), value);
+  } else if (!strcmp(signal_name, "CallChanged")) {
+    const gchar* key = NULL;
+    const gchar* state = NULL;
+    const gchar* line_id = NULL;
+    const gchar* contact = NULL;
+    GVariantIter* iter;
+    GVariant* value;
+
+    g_variant_get(parameters, "(a{sv})", &iter);
+    while (g_variant_iter_next(iter, "{sv}", &key, &value)) {
+      if (!strcmp(key, "state"))
+        state = g_variant_get_string(value, NULL);
+      else if (!strcmp(key, "line_id"))
+        line_id = g_variant_get_string(value, NULL);
+      else if (!strcmp(key, "contact"))
+        contact = g_variant_get_string(value, NULL);
+    }
+    picojson::value::object o;
+    o["state"] = state ? picojson::value(state) : picojson::value("");
+    o["line_id"] = line_id ? picojson::value(line_id) : picojson::value("");
+    o["contact"] = contact ? picojson::value(contact) : picojson::value("");
+    picojson::value v(o);
+    instance->SendSignal(picojson::value(signal_name), v);
+  } else if (!strcmp(signal_name, "CallHistoryChanged") ||
+             !strcmp(signal_name, "ContactsChanged")) {
+    instance->SendSignal(picojson::value(signal_name), picojson::value(""));
+  } else {
+    std::cerr << "Unknown signal: " << signal_name << "\n";
+  }
+}
+
+void PhoneInstance::HandleSelectRemoteDevice(
+    const picojson::value& msg) {
+  if (!msg.contains("address")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  GError* error = NULL;
+  CallDBus("SelectRemoteDevice",
+      g_variant_new("(s)", msg.get("address").to_str().c_str()),
+      &error);
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR, std::string(error->message));
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void PhoneInstance::HandleUnselectRemoteDevice(
+    const picojson::value& msg) {
+  GError* error = NULL;
+  CallDBus("UnselectRemoteDevice", NULL, &error);
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR, std::string(error->message));
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void PhoneInstance::HandleInvokeCall(const picojson::value& msg) {
+  if (!msg.contains("phoneNumber")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  picojson::value::object o;
+  GError* error = NULL;
+  CallDBus("Dial",
+      g_variant_new("(s)", msg.get("phoneNumber").to_str().c_str()),
+      &error);
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR, std::string(error->message));
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void PhoneInstance::HandleAnswerCall(const picojson::value& msg) {
+  GError* error = NULL;
+  CallDBus("Answer", NULL, &error);
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR, std::string(error->message));
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void PhoneInstance::HandleHangupCall(const picojson::value& msg) {
+  GError* error = NULL;
+  CallDBus("Hangup", NULL, &error);
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR, std::string(error->message));
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void PhoneInstance::HandleActiveCall(const picojson::value& msg) {
+  GError* error = NULL;
+  GVariant* reply = NULL;
+  reply = CallDBus("ActiveCall", NULL, &error);
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    SendSyncErrorReply(UNKNOWN_ERR, std::string(error->message));
+  } else if (!reply) {
+    std::cerr << "No reply\n";
+    SendSyncErrorReply(UNKNOWN_ERR);
+  } else {
+    picojson::value::object o;
+    const gchar* key = NULL;
+    const gchar* state = NULL;
+    const gchar* line_id = NULL;
+    const gchar* contact = NULL;
+    GVariantIter* iter;
+    GVariant* value;
+
+    g_variant_get(reply, "(a{sv})", &iter);
+    while (g_variant_iter_next(iter, "{sv}", &key, &value)) {
+      if (!strcmp(key, "state"))
+        state = g_variant_get_string(value, NULL);
+      else if (!strcmp(key, "line_id"))
+        line_id = g_variant_get_string(value, NULL);
+      else if (!strcmp(key, "contact"))
+        contact = g_variant_get_string(value, NULL);
+    }
+
+    o["state"] = state ? picojson::value(state) : picojson::value("");
+    o["line_id"] = line_id ? picojson::value(line_id) : picojson::value("");
+    o["contact"] = contact ? picojson::value(contact) : picojson::value("");
+    picojson::value result(o);
+    SendSyncSuccessReply(result);
+  }
+
+  if (reply)
+    g_variant_unref(reply);
+}
+
+void PhoneInstance::HandleMuteCall(const picojson::value& msg) {
+  if (!msg.contains("mute")) {
+    PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+    return;
+  }
+
+  GError* error = NULL;
+  CallDBus("Mute",
+      g_variant_new("(b)", msg.get("mute").evaluate_as_boolean()),
+      &error);
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR, std::string(error->message));
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void PhoneInstance::HandleGet(const std::string& cmd,
+    const picojson::value& msg) {
+  GError* error = NULL;
+  GVariant* reply = NULL;
+
+  if (cmd == "GetSelectedRemoteDevice") {
+    reply = CallDBus(cmd.c_str(), NULL, &error);
+  } else if (cmd == "GetContacts" || cmd == "GetCallHistory") {
+    if (!msg.contains("count")) {
+      PostAsyncErrorReply(msg, INVALID_VALUES_ERR);
+      return;
+    }
+
+    reply = CallDBus(cmd.c_str(),
+        g_variant_new("(u)", msg.get("count").get<double>()),
+        &error);
+  }
+
+  if (error) {
+    // call the error callback to notify client about the error condition
+    std::cerr << "Error occured '" << error->message << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR, std::string(error->message));
+  } else if (!reply) {
+    std::cerr << "No reply\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR);
+  } else {
+    const gchar* result;
+    g_variant_get(reply, "(s)", &result);
+
+    if (!result) {
+      PostAsyncErrorReply(msg, UNKNOWN_ERR);
+      return;
+    }
+
+    if (cmd == "GetSelectedRemoteDevice") {
+      picojson::value value(result);
+      PostAsyncSuccessReply(msg, value);
+    } else if (cmd == "GetContacts" || cmd == "GetCallHistory") {
+      picojson::value value;
+      std::string err;
+      picojson::parse(value, result, result + strlen(result), &err);
+      if (!err.empty()) {
+        std::cerr << "cannot parse result.\n";
+        PostAsyncErrorReply(msg, UNKNOWN_ERR);
+        g_variant_unref(reply);
+        return;
+      }
+      PostAsyncSuccessReply(msg, value);
+    } else {
+      PostAsyncErrorReply(msg, UNKNOWN_ERR);
+    }
+
+    g_variant_unref(reply);
+  }
+}
+
+void PhoneInstance::HandleAddListener(guint& listener_id,
+    const std::string& signal_name,
+    const picojson::value& msg) {
+  listener_id = g_dbus_connection_signal_subscribe(
+      g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+      kPhoneService,
+      kPhoneInterface,
+      signal_name.c_str(),
+      NULL,
+      NULL,
+      G_DBUS_SIGNAL_FLAGS_NONE,
+      HandleSignal,
+      this,
+      NULL);
+
+  if (listener_id <= 0) {
+    std::cerr << "Failed to subscribe for '" << signal_name << "': "
+              << listener_id << "\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR);
+    return;
+  }
+
+  PostAsyncSuccessReply(msg);
+}
+
+void PhoneInstance::HandleRemoveListener(guint& listener_id,
+    const std::string& signal_name,
+    const picojson::value& msg) {
+  if (listener_id == 0) {
+    std::cerr << "Failed to unsubscribe for '" << signal_name << "'\n";
+    PostAsyncErrorReply(msg, UNKNOWN_ERR);
+    return;
+  }
+
+  g_dbus_connection_signal_unsubscribe(
+      g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
+      listener_id);
+
+  listener_id = 0;
+  PostAsyncSuccessReply(msg);
+}
diff --git a/phone/phone_instance.h b/phone/phone_instance.h
new file mode 100644 (file)
index 0000000..0b1ddde
--- /dev/null
@@ -0,0 +1,87 @@
+// 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 PHONE_PHONE_INSTANCE_H_
+#define PHONE_PHONE_INSTANCE_H_
+
+#include <gio/gio.h>
+#include <string>
+#include <thread> // NOLINT
+
+#include "common/extension.h"
+#include "common/picojson.h"
+#include "tizen/tizen.h"
+
+class PhoneInstance : public common::Instance {
+ public:
+  PhoneInstance();
+  ~PhoneInstance();
+
+ private:
+  // common::Instance implementation.
+  virtual void HandleMessage(const char* msg);
+  virtual void HandleSyncMessage(const char* msg);
+
+  // Synchronous messages
+  void HandleActiveCall(const picojson::value& msg);
+
+  // Asynchronous messages
+  void HandleSelectRemoteDevice(const picojson::value& msg);
+  void HandleUnselectRemoteDevice(const picojson::value& msg);
+  void HandleInvokeCall(const picojson::value& msg);
+  void HandleAnswerCall(const picojson::value& msg);
+  void HandleHangupCall(const picojson::value& msg);
+  void HandleMuteCall(const picojson::value& msg);
+  void HandleGet(const std::string& cmd, const picojson::value& msg);
+  void HandleAddListener(guint& listener_id,
+                         const std::string& signal_name,
+                         const picojson::value& msg);
+  void HandleRemoveListener(guint& listener_id,
+                            const std::string& signal_name,
+                            const picojson::value& msg);
+
+  // Synchronous message helpers
+  void SendSyncErrorReply(WebApiAPIErrors error_code,
+                          const std::string& error_msg);
+  void SendSyncSuccessReply();
+  void SendSyncSuccessReply(const picojson::value& value);
+
+  // Asynchronous message helpers
+  void PostAsyncReply(const picojson::value& msg,
+                      picojson::value::object& value);
+  void PostAsyncErrorReply(const picojson::value& msg,
+                           WebApiAPIErrors error_code,
+                           const std::string& error_msg);
+  void PostAsyncSuccessReply(const picojson::value& msg,
+                             const picojson::value& value);
+  void PostAsyncSuccessReply(const picojson::value& msg);
+
+  void SendSignal(const picojson::value& signal_name,
+                  const picojson::value& signal_value);
+
+  static GVariant* CallDBus(const gchar* method_name,
+                            GVariant* parameters,
+                            GError **error);
+
+  static void HandleSignal(GDBusConnection* connection,
+                           const gchar* sender_name,
+                           const gchar* object_path,
+                           const gchar* interface_name,
+                           const gchar* signal_name,
+                           GVariant* parameters,
+                           gpointer user_data);
+
+  static void RunMainloop(void* data);
+
+  static guint remote_device_selected_listener_id_;
+  static guint call_changed_listener_id_;
+  static guint call_history_entry_added_listener_id_;
+  static guint call_history_changed_listener_id_;
+  static guint contacts_changed_listener_id_;
+
+  GMainLoop* main_loop_;
+  std::thread thread_;
+};
+
+#endif  // PHONE_PHONE_INSTANCE_H_
index 3d5f783..f36e682 100644 (file)
@@ -12,6 +12,7 @@
         'mediaserver/mediaserver.gyp:*',
         'network_bearer_selection/network_bearer_selection.gyp:*',
         'notification/notification.gyp:*',
+        'phone/phone.gyp:*',
         'power/power.gyp:*',
         'speech/speech.gyp:*',
         'system_info/system_info.gyp:*',