[Bluetooth] Implement API based on Tizen bluetooth framework
authorCorentin Lecouvey <corentin.lecouvey@open.eurogiciel.org>
Wed, 4 Jun 2014 16:05:32 +0000 (18:05 +0200)
committerCorentin Lecouvey <corentin.lecouvey@open.eurogiciel.org>
Tue, 24 Jun 2014 17:46:00 +0000 (19:46 +0200)
Make 3 bluetooth extension backends : bluez4, bluez5 and tizen_capi

- add bluetooth_instance_capi.* source code using Tizen bluetooth C API
- remove Tizen C API calls from bluez4 backend
- fix bluez5 backend build
- modify device class major, minor and services JSON messages

BUG=XWALK-1065

Signed-off-by: Corentin Lecouvey <corentin.lecouvey@open.eurogiciel.org>
bluetooth/bluetooth.gyp
bluetooth/bluetooth_api.js
bluetooth/bluetooth_extension.cc
bluetooth/bluetooth_instance_bluez4.cc
bluetooth/bluetooth_instance_bluez5.cc
bluetooth/bluetooth_instance_capi.cc [new file with mode: 0644]
bluetooth/bluetooth_instance_capi.h [new file with mode: 0644]
examples/bluetooth.html

index b4db8b2..d05f60f 100644 (file)
@@ -11,7 +11,7 @@
           'gio-2.0',
           'bluez',
         ],
-        'bluetooth%': 'bluez4',
+        'bluetooth%': 'tizen_capi',
       },
       'includes': [
         '../common/pkg-config.gypi',
         'bluetooth_instance.h',
       ],
       'conditions': [
+        [ 'bluetooth == "bluez5" or bluetooth == "bluez4"', {
+          'variables': {
+            'packages': [
+             'gio-2.0',
+             'bluez',
+           ],
+          },
+        }],
         [ 'bluetooth == "bluez5"', {
-            'sources': ['bluetooth_instance_bluez5.cc'],
+            'sources': [
+              'bluetooth_instance_bluez5.cc',
+            ],
             'defines': ['BLUEZ_5'],
           }
         ],
         [ 'bluetooth == "bluez4"', {
-            'sources': ['bluetooth_instance_bluez4.cc'],
+            'sources': [
+              'bluetooth_instance_bluez4.cc',
+            ],
             'defines': ['BLUEZ_4'],
           }
         ],
-        [ 'tizen == 1', {
-            'variables': { 'packages': ['capi-network-bluetooth'] },
-        }],
+        [ 'bluetooth == "tizen_capi"', {
+            'sources!': [
+              'bluetooth_instance.cc',
+              'bluetooth_instance.h',
+            ],
+            'sources': [
+              'bluetooth_instance_capi.cc',
+              'bluetooth_instance_capi.h',
+            ],
+            'variables': {
+              'packages': [
+                'capi-network-bluetooth',
+                'glib-2.0',
+              ]
+            },
+            'defines': ['TIZEN_CAPI_BT'],
+          }
+        ],
       ],
     },
   ],
index fe981f8..5861917 100644 (file)
@@ -15,8 +15,9 @@ var postMessage = function(msg, callback) {
 
 extension.setMessageListener(function(json) {
   var msg = JSON.parse(json);
-
-  if (msg.cmd == 'DeviceFound')
+  if (msg.cmd == 'BondedDevice')
+    handleBondedDevice(msg);
+  else if (msg.cmd == 'DeviceFound')
     handleDeviceFound(msg);
   else if (msg.cmd == 'DiscoveryFinished')
     handleDiscoveryFinished();
@@ -40,7 +41,9 @@ extension.setMessageListener(function(json) {
       delete _callbacks[reply_id];
       callback(msg);
     } else {
-      console.log('Invalid reply_id from Tizen Bluetooth: ' + reply_id);
+      // do not print error log when the postmessage was not initiated by JS
+      if (reply_id != '')
+        console.log('Invalid reply_id from Tizen Bluetooth: ' + reply_id);
     }
   }
 });
@@ -55,50 +58,6 @@ function Adapter() {
   this.change_listener = null;
 }
 
-
-var signature_to_type = { 'n': 'number',
-                          'f': 'function',
-                          'b': 'boolean',
-                          's': 'string',
-                          'o': 'object'
-                        };
-
-// Returns if the passed arguments match the signature.
-function validateArguments(signature, args) {
-  var full_args = Array.prototype.slice.call(args);
-
-  // After '?' everything is optional.
-  var mandatory_len = signature.indexOf('?') === -1 ? signature.length : signature.indexOf('?');
-
-  if (full_args.length < mandatory_len)
-    return false;
-
-  // Mandatory arguments.
-  for (var i = 0; i < mandatory_len; i++) {
-    if (typeof full_args[i] !== signature_to_type[signature[i]] || full_args[i] === null)
-      return false;
-  }
-
-  // Optional args may be null.
-  for (var i = mandatory_len; i < full_args.length && i < signature.length - 1; i++) {
-    if (full_args[i] !== null && typeof full_args[i] !== signature_to_type[signature[i + 1]])
-      return false;
-  }
-
-  return true;
-}
-
-function validateObject(object, signature, attributes) {
-  for (var i = 0; i < signature.length; i++) {
-    if (object.hasOwnProperty(attributes[i]) &&
-        typeof object[attributes[i]] !== signature_to_type[signature[i]]) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
 function validateAddress(address) {
   if (typeof address !== 'string')
     return false;
@@ -176,6 +135,11 @@ var deepCopyDevices = function(devices) {
   return copiedDevices;
 };
 
+var handleBondedDevice = function(msg) {
+  var device = new BluetoothDevice(msg);
+  adapter.addDevice(device, false);
+};
+
 var handleDeviceFound = function(msg) {
   var device = new BluetoothDevice(msg);
   var is_new = adapter.addDevice(device, msg.found_on_discovery);
@@ -256,7 +220,10 @@ var handleAdapterUpdated = function(msg) {
 var handleRFCOMMSocketAccept = function(msg) {
   for (var i in adapter.service_handlers) {
     var server = adapter.service_handlers[i];
-    if (server.channel === msg.channel) {
+    // FIXME(clecou) BlueZ4 backend compares rfcomm channel number but this parameter
+    // is not available in Tizen C API so we check socket fd.
+    // A better approach would be to adapt backends instances to have a single JSON protocol.
+    if (server.channel === msg.channel || server.server_fd === msg.socket_fd) {
       var j = adapter.indexOfDevice(adapter.known_devices, msg.peer);
       var peer = adapter.known_devices[j];
 
@@ -448,7 +415,7 @@ function BluetoothAdapter() {
 }
 
 BluetoothAdapter.prototype.setName = function(name, successCallback, errorCallback) {
-  if (!validateArguments('s?ff', arguments)) {
+  if (!xwalk.utils.validateArguments('s?ff', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -480,7 +447,7 @@ BluetoothAdapter.prototype.setName = function(name, successCallback, errorCallba
 };
 
 BluetoothAdapter.prototype.setPowered = function(state, successCallback, errorCallback) {
-  if (!validateArguments('b?ff', arguments)) {
+  if (!xwalk.utils.validateArguments('b?ff', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -514,7 +481,7 @@ BluetoothAdapter.prototype.setPowered = function(state, successCallback, errorCa
 };
 
 BluetoothAdapter.prototype.setVisible = function(mode, successCallback, errorCallback, timeout) {
-  if (!validateArguments('b?ffn', arguments)) {
+  if (!xwalk.utils.validateArguments('b?ffn', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -550,12 +517,12 @@ BluetoothAdapter.prototype.setVisible = function(mode, successCallback, errorCal
 };
 
 BluetoothAdapter.prototype.discoverDevices = function(discoverySuccessCallback, errorCallback) {
-  if (!validateArguments('o?f', arguments)) {
+  if (!xwalk.utils.validateArguments('o?f', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
-  if (!validateObject(discoverySuccessCallback, 'ffff',
-                      ['onstarted', 'ondevicefound', 'ondevicedisappeared', 'onfinished'])) {
+  if (!xwalk.utils.validateObject(discoverySuccessCallback, 'ffff',
+      ['onstarted', 'ondevicefound', 'ondevicedisappeared', 'onfinished'])) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -582,7 +549,7 @@ BluetoothAdapter.prototype.discoverDevices = function(discoverySuccessCallback,
 };
 
 BluetoothAdapter.prototype.stopDiscovery = function(successCallback, errorCallback) {
-  if (!validateArguments('?ff', arguments)) {
+  if (!xwalk.utils.validateArguments('?ff', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -609,7 +576,7 @@ BluetoothAdapter.prototype.stopDiscovery = function(successCallback, errorCallba
 };
 
 BluetoothAdapter.prototype.getKnownDevices = function(deviceArraySuccessCallback, errorCallback) {
-  if (!validateArguments('f?f', arguments)) {
+  if (!xwalk.utils.validateArguments('f?f', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -622,7 +589,7 @@ BluetoothAdapter.prototype.getKnownDevices = function(deviceArraySuccessCallback
 };
 
 BluetoothAdapter.prototype.getDevice = function(address, deviceSuccessCallback, errorCallback) {
-  if (!validateArguments('sf?f', arguments)) {
+  if (!xwalk.utils.validateArguments('sf?f', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -644,7 +611,7 @@ BluetoothAdapter.prototype.getDevice = function(address, deviceSuccessCallback,
 };
 
 BluetoothAdapter.prototype.createBonding = function(address, successCallback, errorCallback) {
-  if (!validateArguments('sf?f', arguments)) {
+  if (!xwalk.utils.validateArguments('sf?f', arguments)) {
     throw new tizen.WebAPIError(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -682,13 +649,19 @@ BluetoothAdapter.prototype.createBonding = function(address, successCallback, er
         }
       }
 
+      // FIXME(clecou) Update known device state here when using C API Tizen backend
+      // BlueZ backends update the device state automatically when catching dbus signals.
+      // A better approach would be to adapt backends instances to have a single JSON protocol.
+      if (result.capi)
+        _addConstProperty(adapter.known_devices[i], 'isBonded', true);
+
       successCallback(cb_device);
     }
   });
 };
 
 BluetoothAdapter.prototype.destroyBonding = function(address, successCallback, errorCallback) {
-  if (!validateArguments('s?ff', arguments)) {
+  if (!xwalk.utils.validateArguments('s?ff', arguments)) {
     throw new tizen.WebAPIError(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -728,6 +701,12 @@ BluetoothAdapter.prototype.destroyBonding = function(address, successCallback, e
         }
       }
 
+      // FIXME(clecou) Update known device state here when using C API Tizen backend
+      // BlueZ backends update the device state automatically when catching dbus signals
+      // A better approach would be to adapt backends instances to have a single JSON protocol.
+      if (result.capi)
+        _addConstProperty(adapter.known_devices[i], 'isBonded', false);
+
       successCallback(cb_device);
     }
   });
@@ -735,7 +714,7 @@ BluetoothAdapter.prototype.destroyBonding = function(address, successCallback, e
 
 BluetoothAdapter.prototype.registerRFCOMMServiceByUUID =
     function(uuid, name, serviceSuccessCallback, errorCallback) {
-  if (!validateArguments('ssf?f', arguments)) {
+  if (!xwalk.utils.validateArguments('ssf?f', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -771,12 +750,12 @@ BluetoothAdapter.prototype.registerRFCOMMServiceByUUID =
 };
 
 BluetoothAdapter.prototype.setChangeListener = function(listener) {
-  if (!validateArguments('o', arguments)) {
+  if (!xwalk.utils.validateArguments('o', arguments)) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
-  if (!validateObject(listener, 'fff',
-                      ['onstatechanged', 'onnamechanged', 'onvisibilitychanged'])) {
+  if (!xwalk.utils.validateObject(listener, 'fff',
+      ['onstatechanged', 'onnamechanged', 'onvisibilitychanged'])) {
     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -809,24 +788,31 @@ function BluetoothDevice(msg) {
   _addConstProperty(this, 'address', msg.Address);
 
   _addConstProperty(this, 'deviceClass', new BluetoothClass());
-  _addConstProperty(this.deviceClass, 'minor', (msg.Class >> 2) & _deviceClassMask.MINOR);
-  _addConstProperty(this.deviceClass, 'major', (msg.Class >> 8) & _deviceClassMask.MAJOR);
+  _addConstProperty(this.deviceClass, 'minor', (msg.ClassMinor >> 2) & _deviceClassMask.MINOR);
+  _addConstProperty(this.deviceClass, 'major', msg.ClassMajor & _deviceClassMask.MAJOR);
 
   _addConstProperty(this, 'isBonded', (msg.Paired == 'true') ? true : false);
   _addConstProperty(this, 'isTrusted', (msg.Trusted == 'true') ? true : false);
   _addConstProperty(this, 'isConnected', (msg.Connected == 'true') ? true : false);
-  // Parse UUIDs
-  var uuids_array = [];
+
   if (msg.UUIDs) {
-    uuids_array = msg.UUIDs.substring(msg.UUIDs.indexOf('[') + 1,
-        msg.UUIDs.indexOf(']')).split(',');
-    for (var i = 0; i < uuids_array.length; i++) {
-      uuids_array[i] = uuids_array[i].substring(2, uuids_array[i].length - 1);
+    if (typeof msg.UUIDs === 'string') {
+      // FIXME(clecou) BlueZ backend sends a string to convert it into an array
+      // A better approach would be to adapt backends instances to have a single JSON protocol.
+      var uuids_array = [];
+      uuids_array = msg.UUIDs.substring(msg.UUIDs.indexOf('[') + 1,
+          msg.UUIDs.indexOf(']')).split(',');
+      for (var i = 0; i < uuids_array.length; i++) {
+        uuids_array[i] = uuids_array[i].substring(2, uuids_array[i].length - 1);
+      }
+      _addConstProperty(this, 'uuids', uuids_array);
+    } else {
+      // Tizen C API backend directly sends an array
+      _addConstProperty(this, 'uuids', msg.UUIDs);
     }
   }
-  _addConstProperty(this, 'uuids', uuids_array);
 
-  var services = (msg.Class >> 13) & _deviceClassMask.SERVICE;
+  var services = (msg.ClassService >> 13) & _deviceClassMask.SERVICE;
   var services_array = [];
 
   // 11 is the number of bits in _deviceClassMask.SERVICE
@@ -838,7 +824,50 @@ function BluetoothDevice(msg) {
 }
 
 BluetoothDevice.prototype.connectToServiceByUUID =
-    function(uuid, socketSuccessCallback, errorCallback) {};
+    function(uuid, socketSuccessCallback, errorCallback) {
+
+  if (!xwalk.utils.validateArguments('sf?f', arguments)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+
+  if (adapter.checkServiceAvailability(errorCallback))
+    return;
+
+  var uuid_found = false;
+  for (var i = 0; i < this.uuids.length; i++) {
+    if (this.uuids[i] == uuid) {
+      uuid_found = true;
+      break;
+    }
+  }
+  if (uuid_found == false) {
+    var error = new tizen.WebAPIError(tizen.WebAPIException.NOT_FOUND_ERR);
+    errorCallback(error);
+  }
+
+  var msg = {
+    'cmd': 'ConnectToService',
+    'uuid': uuid,
+    'address' : this.address
+  };
+
+  postMessage(msg, function(result) {
+    if (result.error != 0) {
+      if (errorCallback) {
+        var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
+        errorCallback(error);
+      }
+
+      throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+      return;
+    }
+
+    if (socketSuccessCallback) {
+      var socket_cb = new BluetoothSocket(result.uuid, this, result);
+      socketSuccessCallback(socket_cb);
+    }
+  });
+};
 
 BluetoothDevice.prototype._clone = function() {
   var clone = new BluetoothDevice();
@@ -906,6 +935,12 @@ Object.defineProperty(BluetoothSocket, 'BluetoothSocketState', {
 
 
 BluetoothSocket.prototype.writeData = function(data) {
+  // make sure that socket is connected and opened.
+  if (this.state == BluetoothSocketState.CLOSE) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
+    return;
+  }
+
   var msg = {
     'cmd': 'SocketWriteData',
     'data': data,
@@ -927,8 +962,26 @@ BluetoothSocket.prototype.close = function() {
   };
 
   postMessage(msg, function(result) {
-    if (result.error)
+    if (result.error) {
       console.log('Can\'t close socket (' + this.socket_fd + ').');
+      throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
+      return;
+    }
+
+    // FIXME(clecou) Update socket object state only when using Tizen C API backend.
+    // BlueZ4 backend independently updates socket state based on a dbus callback mechanism.
+    // A better approach would be to adapt backends instances to have a single JSON protocol.
+    if (result.capi) {
+      for (var i in adapter.sockets) {
+        var socket = adapter.sockets[i];
+        if (socket.socket_fd === msg.socket_fd) {
+          if (socket.onclose && typeof socket.onmessage === 'function') {
+            _addConstProperty(adapter.sockets[i], 'state', BluetoothSocketState.CLOSE);
+            socket.onclose();
+          }
+        }
+      }
+    }
   });
 };
 
@@ -954,7 +1007,7 @@ function BluetoothServiceHandler(name, uuid, msg) {
 }
 
 BluetoothServiceHandler.prototype.unregister = function(successCallback, errorCallback) {
-  if (!validateArguments('?ff', arguments)) {
+  if (!xwalk.utils.validateArguments('?ff', arguments)) {
     throw new tizen.WebAPIError(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
 
@@ -978,8 +1031,8 @@ BluetoothServiceHandler.prototype.unregister = function(successCallback, errorCa
       return;
     }
 
-    if (successCallback) {
+    _addConstProperty(this, 'isConnected', false);
+    if (successCallback)
       successCallback();
-    }
   });
 };
index f66197f..f0749bc 100644 (file)
@@ -4,17 +4,16 @@
 
 #include "bluetooth/bluetooth_extension.h"
 
-#if defined(TIZEN)
+#if defined(TIZEN_CAPI_BT)
 #include <bluetooth.h>
-#endif
-
+#include "bluetooth/bluetooth_instance_capi.h"
+#else
 #include "bluetooth/bluetooth_instance.h"
+#endif
 
 common::Extension* CreateExtension() {
-#if defined(TIZEN)
-  int init = bt_initialize();
-  if (init != BT_ERROR_NONE)
-    g_printerr("\n\nCouldn't initialize Bluetooth module.");
+#if defined(TIZEN_CAPI_BT)
+  CAPI(bt_initialize());
 #endif
 
   return new BluetoothExtension;
index 1317a9c..c008e8a 100644 (file)
@@ -4,10 +4,6 @@
 
 #include "bluetooth/bluetooth_instance.h"
 
-#if defined(TIZEN)
-#include <bluetooth.h>
-#endif
-
 #include <sys/types.h>
 #include <sys/socket.h>
 
@@ -120,7 +116,12 @@ static void getPropertyValue(const char* key, GVariant* value,
     picojson::value::object& o) {
   if (!strcmp(key, "Class")) {
     guint32 class_id = g_variant_get_uint32(value);
-    o[key] = picojson::value(static_cast<double>(class_id));
+    o["ClassMajor"] = picojson::value \
+                      (static_cast<double>((class_id & 0x00001F00) >> 8));
+    o["ClassMinor"] = picojson::value \
+                      (static_cast<double>(class_id & 0x000000FC));
+    o["ClassService"] = picojson::value\
+                        (static_cast<double>(class_id & 0x00FF0000));
   } else if (!strcmp(key, "RSSI")) {
     gint16 class_id = g_variant_get_int16(value);
     o[key] = picojson::value(static_cast<double>(class_id));
@@ -453,13 +454,6 @@ void BluetoothInstance::AdapterSetPowered(const picojson::value& msg) {
   bool powered = msg.get("value").get<bool>();
   int error = 0;
 
-#if defined(TIZEN)
-  if (powered)
-    error = bt_adapter_enable();
-  else
-    error = bt_adapter_disable();
-#else
-
   OnAdapterPropertySetData* property_set_callback_data_ =
       new OnAdapterPropertySetData;
   property_set_callback_data_->property = std::string("Powered");
@@ -472,7 +466,6 @@ void BluetoothInstance::AdapterSetPowered(const picojson::value& msg) {
                     G_DBUS_CALL_FLAGS_NONE, 5000, all_pending_,
                     OnAdapterPropertySetThunk,
                     property_set_callback_data_);
-#endif
 
   // Reply right away in case of error, or powered off.
   if (error || powered == false) {
@@ -615,10 +608,6 @@ BluetoothInstance::~BluetoothInstance() {
     g_object_unref(it->second);
 
   g_bus_unwatch_name(name_watch_id_);
-
-#if defined(TIZEN)
-    bt_deinitialize();
-#endif
 }
 
 void BluetoothInstance::PlatformInitialize() {
index c7b56a2..58330af 100644 (file)
@@ -10,7 +10,12 @@ static void getPropertyValue(const char* key, GVariant* value,
     picojson::value::object& o) {
   if (!strcmp(key, "Class")) {
     guint32 class_id = g_variant_get_uint32(value);
-    o[key] = picojson::value(static_cast<double>(class_id));
+    o["ClassMajor"] = picojson::value \
+                      (static_cast<double>((class_id & 0x00001F00) >> 8));
+    o["ClassMinor"] = picojson::value \
+                      (static_cast<double>(class_id & 0x000000FC));
+    o["ClassService"] = picojson::value\
+                        (static_cast<double>(class_id & 0x00FF0000));
   } else if (!strcmp(key, "RSSI")) {
     gint16 class_id = g_variant_get_int16(value);
     o[key] = picojson::value(static_cast<double>(class_id));
@@ -192,10 +197,10 @@ void BluetoothInstance::PlatformInitialize() {
       this);
 }
 
-picojson::value BluetoothInstance::HandleGetDefaultAdapter(
+void BluetoothInstance::HandleGetDefaultAdapter(
     const picojson::value& msg) {
   if (adapter_info_.empty())
-    return picojson::value();
+    return;
 
   picojson::value::object o;
   o["cmd"] = picojson::value("");
@@ -215,8 +220,7 @@ picojson::value BluetoothInstance::HandleGetDefaultAdapter(
   if (!is_js_context_initialized_)
     is_js_context_initialized_ = true;
 
-  picojson::value v(o);
-  return v;
+  InternalSetSyncReply(picojson::value(o));
 }
 
 GDBusProxy* BluetoothInstance::CreateDeviceProxy(GAsyncResult* res) {
diff --git a/bluetooth/bluetooth_instance_capi.cc b/bluetooth/bluetooth_instance_capi.cc
new file mode 100644 (file)
index 0000000..fdef401
--- /dev/null
@@ -0,0 +1,700 @@
+// 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 "bluetooth/bluetooth_instance_capi.h"
+
+#include <pthread.h>
+
+#include "common/picojson.h"
+
+namespace {
+
+inline const char* BoolToString(bool b) {
+  return b ? "true" : "false";
+}
+
+static void* event_loop(void* arg) {
+  GMainLoop* event_loop;
+  event_loop = g_main_loop_new(NULL, FALSE);
+  g_main_loop_run(event_loop);
+  return NULL;
+}
+
+}  // anonymous namespace
+
+BluetoothInstance::BluetoothInstance()
+    : is_js_context_initialized_(false),
+      adapter_enabled_(false),
+      js_reply_needed_(false),
+      stop_discovery_from_js_(false) {
+
+  // we need a thread for running the main loop
+  // and catching bluetooth glib signals
+  pthread_t event_thread;
+  pthread_attr_t thread_attr;
+  pthread_attr_init(&thread_attr);
+  pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
+  event_thread = pthread_create(&event_thread, &thread_attr,
+      event_loop, NULL);
+
+  InitializeAdapter();
+}
+
+BluetoothInstance::~BluetoothInstance() {
+  UninitializeAdapter();
+  CAPI(bt_deinitialize());
+}
+
+void BluetoothInstance::HandleMessage(const char* message) {
+  picojson::value v;
+
+  std::string err;
+  picojson::parse(v, message, message + strlen(message), &err);
+  if (!err.empty()) {
+    LOG_ERR("Ignoring message");
+    return;
+  }
+
+  std::string cmd = v.get("cmd").to_str();
+  if (cmd == "DiscoverDevices")
+    HandleDiscoverDevices(v);
+  else if (cmd == "StopDiscovery")
+    HandleStopDiscovery(v);
+  else if (cmd == "SetAdapterProperty")
+    HandleSetAdapterProperty(v);
+  else if (cmd == "CreateBonding")
+    HandleCreateBonding(v);
+  else if (cmd == "DestroyBonding")
+    HandleDestroyBonding(v);
+  else if (cmd == "RFCOMMListen")
+    HandleRFCOMMListen(v);
+  else if (cmd == "CloseSocket")
+    HandleCloseSocket(v);
+  else if (cmd == "UnregisterServer")
+    HandleUnregisterServer(v);
+}
+
+void BluetoothInstance::HandleSyncMessage(const char* message) {
+  picojson::value v;
+
+  std::string err;
+  picojson::parse(v, message, message + strlen(message), &err);
+  if (!err.empty()) {
+    LOG_ERR("Ignoring Sync message.");
+    return;
+  }
+
+  std::string cmd = v.get("cmd").to_str();
+  if (cmd == "GetDefaultAdapter")
+    HandleGetDefaultAdapter(v);
+  else if (cmd == "SocketWriteData")
+    HandleSocketWriteData(v);
+}
+
+void BluetoothInstance::OnStateChanged(int result,
+    bt_adapter_state_e adapter_state, void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+
+  obj->adapter_enabled_ = (adapter_state == BT_ADAPTER_ENABLED) ? true : false;
+
+  if (obj->js_reply_needed_) {
+    // FIXME(clecou) directly call 'GetDefaultAdapter' once NTB is integrated.
+    // After testing, 100 ms is necessary to really get a powered adapter.
+    g_timeout_add(100, obj->GetDefaultAdapter, obj);
+    return;
+  }
+
+  picojson::value::object o;
+
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["Powered"]);
+  if (result)
+    o["error"] = picojson::value(static_cast<double>(1));
+  else
+    o["error"] = picojson::value(static_cast<double>(0));
+
+  obj->InternalPostMessage(picojson::value(o));
+  obj->callbacks_id_map_.erase("Powered");
+}
+
+void BluetoothInstance::OnNameChanged(char* name, void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+
+  picojson::value::object o;
+
+  o["error"] = picojson::value(static_cast<double>(0));
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["Name"]);
+  obj->InternalPostMessage(picojson::value(o));
+  obj->callbacks_id_map_.erase("Name");
+}
+
+void BluetoothInstance::OnVisibilityChanged(int result,
+    bt_adapter_visibility_mode_e visibility_mode, void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+
+  picojson::value::object o;
+
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["Discoverable"]);
+  if (result)
+    o["error"] = picojson::value(static_cast<double>(1));
+  else
+    o["error"] = picojson::value(static_cast<double>(0));
+
+  obj->InternalPostMessage(picojson::value(o));
+  obj->callbacks_id_map_.erase("Discoverable");
+}
+
+void BluetoothInstance::OnDiscoveryStateChanged(int result,
+    bt_adapter_device_discovery_state_e discovery_state,
+    bt_adapter_device_discovery_info_s* discovery_info, void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+
+  picojson::value::object o;
+
+  switch (discovery_state) {
+    case BT_ADAPTER_DEVICE_DISCOVERY_STARTED: {
+      o["cmd"] = picojson::value("");
+      o["reply_id"] = picojson::value(obj->callbacks_id_map_["StartDiscovery"]);
+      if (result) {
+        LOG_ERR(result);
+        o["error"] = picojson::value(static_cast<double>(1));
+      } else {
+        o["error"] = picojson::value(static_cast<double>(0));
+      }
+      obj->InternalPostMessage(picojson::value(o));
+      obj->callbacks_id_map_.erase("StartDiscovery");
+      break;
+    }
+    case BT_ADAPTER_DEVICE_DISCOVERY_FINISHED: {
+      if (obj->stop_discovery_from_js_) {
+        o["cmd"] = picojson::value("");
+        o["reply_id"] =
+            picojson::value(obj->callbacks_id_map_["StopDiscovery"]);
+        if (result) {
+          LOG_ERR(result);
+          o["error"] = picojson::value(static_cast<double>(1));
+        } else {
+          o["error"] = picojson::value(static_cast<double>(0));
+        }
+      } else {
+        // discovery stop was not initiated by JS. It was done by a timeout...
+        o["cmd"] = picojson::value("DiscoveryFinished");
+      }
+      obj->InternalPostMessage(picojson::value(o));
+      obj->callbacks_id_map_.erase("StopDiscovery");
+      obj->stop_discovery_from_js_ = false;
+      break;
+    }
+    case BT_ADAPTER_DEVICE_DISCOVERY_FOUND: {
+      o["Alias"] = picojson::value(discovery_info->remote_name);
+      o["Address"] = picojson::value(discovery_info->remote_address);
+
+      int major = discovery_info->bt_class.major_device_class;
+      int minor = discovery_info->bt_class.minor_device_class;
+      int service_class = discovery_info->bt_class.major_service_class_mask;
+      o["ClassMajor"] = picojson::value(static_cast<double>(major));
+      o["ClassMinor"] = picojson::value(static_cast<double>(minor));
+      o["ClassService"] = picojson::value(static_cast<double>(service_class));
+
+      picojson::array uuids;
+      for (int i = 0; i < discovery_info->service_count; i++)
+        uuids.push_back(picojson::value(discovery_info->service_uuid[i]));
+
+      o["UUIDs"] = picojson::value(uuids);
+
+      bool paired = false;
+      bool trusted = false;
+      bool connected = false;
+
+      if (discovery_info->is_bonded) {
+        bt_device_info_s* device_info = NULL;
+        CAPI(bt_adapter_get_bonded_device_info(discovery_info->remote_address,
+                                               &device_info));
+        if (!device_info)
+          LOG_ERR("device_info is NULL");
+
+        if (!device_info->is_bonded)
+          LOG_ERR("remote device should be bonded!");
+
+        paired = true;
+        trusted = device_info->is_authorized;
+        connected = device_info->is_connected;
+        CAPI(bt_adapter_free_device_info(device_info));
+      }
+
+      o["Paired"] = picojson::value(BoolToString(paired));
+      o["Trusted"] = picojson::value(BoolToString(trusted));
+      o["Connected"] = picojson::value(BoolToString(connected));
+
+      o["cmd"] = picojson::value("DeviceFound");
+      o["found_on_discovery"] = picojson::value(true);
+
+      obj->InternalPostMessage(picojson::value(o));
+      break;
+    }
+    default:
+      LOG_ERR("Unknown discovery state callback!");
+      break;
+  }
+}
+
+bool BluetoothInstance::OnKnownBondedDevice(bt_device_info_s* device_info,
+    void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL!");
+    return false;
+  }
+  if (!device_info) {
+    LOG_ERR("device_info is NULL!");
+    return false;
+  }
+
+  picojson::value::object o;
+  char* alias = device_info->remote_name;
+  o["Alias"] = picojson::value(alias);
+
+  char* address = device_info->remote_address;
+  o["Address"] = picojson::value(address);
+
+  int major = device_info->bt_class.major_device_class;
+  int minor = device_info->bt_class.minor_device_class;
+  int service_class = device_info->bt_class.major_service_class_mask;
+  o["ClassMajor"] = picojson::value(static_cast<double>(major));
+  o["ClassMinor"] = picojson::value(static_cast<double>(minor));
+  o["ClassService"] = picojson::value(static_cast<double>(service_class));
+
+  // parse UUIDs supported by remote device
+  picojson::array uuids;
+  for (int i = 0; i < device_info->service_count; i++)
+    uuids.push_back(picojson::value(device_info->service_uuid[i]));
+
+  o["UUIDs"] = picojson::value(uuids);
+  o["Paired"] = picojson::value(BoolToString(device_info->is_bonded));
+  o["Trusted"] = picojson::value(BoolToString(device_info->is_authorized));
+  o["Connected"] = picojson::value(BoolToString(device_info->is_connected));
+  o["reply_id"] = picojson::value("");
+  o["cmd"] = picojson::value("BondedDevice");
+  obj->InternalPostMessage(picojson::value(o));
+  return true;
+}
+
+void BluetoothInstance::OnBondCreated(int result, bt_device_info_s* device_info,
+    void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL!");
+    return;
+  }
+  if (!device_info) {
+    LOG_ERR("device_info is NULL!");
+    return;
+  }
+
+  picojson::value::object o;
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["CreateBonding"]);
+  o["capi"] = picojson::value(static_cast<double>(1));
+  if (result) {
+    LOG_ERR("onBondCreated() failed");
+    o["error"] = picojson::value(static_cast<double>(1));
+  } else {
+    o["error"] = picojson::value(static_cast<double>(0));
+  }
+  obj->InternalPostMessage(picojson::value(o));
+  obj->callbacks_id_map_.erase("CreateBonding");
+}
+
+void BluetoothInstance::OnBondDestroyed(int result, char* remote_address,
+    void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL!");
+    return;
+  }
+
+  if (!remote_address) {
+    LOG_ERR("remote_address is NULL!");
+    return;
+  }
+  picojson::value::object o;
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = picojson::value(obj->callbacks_id_map_["DestroyBonding"]);
+  o["capi"] = picojson::value(static_cast<double>(1));
+  if (result) {
+    LOG_ERR("onBondDestroyed() failed");
+    o["error"] = picojson::value(static_cast<double>(1));
+  } else {
+    o["error"] = picojson::value(static_cast<double>(0));
+  }
+  obj->InternalPostMessage(picojson::value(o));
+  obj->callbacks_id_map_.erase("DestroyBonding");
+}
+
+void BluetoothInstance::OnSocketConnected(int result,
+    bt_socket_connection_state_e connection_state,
+    bt_socket_connection_s* connection,
+    void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL!");
+    return;
+  }
+  if (!connection) {
+    LOG_ERR("connection is NULL!");
+    return;
+  }
+
+  picojson::value::object o;
+
+  if (result) {
+    LOG_ERR("onSocketConnected() is failed");
+    o["error"] = picojson::value(static_cast<double>(1));
+  }
+
+  if (connection_state == BT_SOCKET_CONNECTED &&
+      connection->local_role == BT_SOCKET_SERVER) {
+    o["cmd"] = picojson::value("RFCOMMSocketAccept");
+    o["uuid"] = picojson::value(connection->service_uuid);
+    o["socket_fd"] =
+        picojson::value(static_cast<double>(connection->socket_fd));
+    o["peer"] = picojson::value(connection->remote_address);
+
+    CAPI(bt_socket_set_data_received_cb(OnSocketHasData, NULL));
+  } else if (connection_state == BT_SOCKET_CONNECTED &&
+             connection->local_role == BT_SOCKET_CLIENT) {
+    o["cmd"] = picojson::value("");
+    o["reply_id"] =
+        picojson::value(obj->callbacks_id_map_["ConnectToService"]);
+    obj->callbacks_id_map_.erase("ConnectToService");
+
+    o["uuid"] = picojson::value(connection->service_uuid);
+    o["socket_fd"] =
+        picojson::value(static_cast<double>(connection->socket_fd));
+
+    CAPI(bt_socket_set_data_received_cb(OnSocketHasData, NULL));
+  } else if (connection_state == BT_SOCKET_DISCONNECTED) {
+      o["cmd"] = picojson::value("");
+      o["reply_id"] =
+          picojson::value(obj->callbacks_id_map_["RFCOMMsocketDestroy"]);
+      obj->callbacks_id_map_.erase("RFCOMMsocketDestroy");
+      o["socket_fd"] =
+          picojson::value(static_cast<double>(connection->socket_fd));
+  } else {
+    LOG_ERR("Unknown role!");
+    return;
+  }
+  obj->InternalPostMessage(picojson::value(o));
+}
+
+void BluetoothInstance::OnSocketHasData(bt_socket_received_data_s* data,
+                                        void* user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return;
+  }
+  if (!data) {
+    LOG_ERR("data is NULL");
+    return;
+  }
+  picojson::value::object o;
+  o["cmd"] = picojson::value("SocketHasData");
+  o["socket_fd"] = picojson::value(static_cast<double>(data->socket_fd));
+  o["data"] = picojson::value(static_cast<std::string>(data->data));
+  obj->InternalPostMessage(picojson::value(o));
+}
+
+gboolean BluetoothInstance::GetDefaultAdapter(gpointer user_data) {
+  BluetoothInstance* obj = static_cast<BluetoothInstance*>(user_data);
+  if (!obj) {
+    LOG_ERR("user_data is NULL");
+    return TRUE;
+  }
+
+  picojson::value::object o;
+
+  char* name = NULL;
+  CAPI(bt_adapter_get_name(&name));
+  if (!name)
+    return TRUE;
+  o["name"] = picojson::value(name);
+
+  char* address = NULL;
+  CAPI(bt_adapter_get_address(&address));
+  if (!address)
+    return TRUE;
+  o["address"] = picojson::value(address);
+
+  bool powered, visible = false;
+
+  if (obj->adapter_enabled_) {
+    powered = true;
+
+  bt_adapter_visibility_mode_e mode =
+      BT_ADAPTER_VISIBILITY_MODE_NON_DISCOVERABLE;
+
+  CAPI(bt_adapter_get_visibility(&mode, NULL));
+  visible = (mode > 0) ? true : false;
+  }
+  o["powered"] = picojson::value(powered);
+  o["visible"] = picojson::value(visible);
+
+  // This is the JS API entry point, so we should clean our message queue
+  // on the next PostMessage call.
+  if (!obj->is_js_context_initialized_)
+    obj->is_js_context_initialized_ = true;
+
+  obj->InternalSetSyncReply(picojson::value(o));
+
+  // Retrieve already bonded devices linked to the adapter in order to
+  // fill known_devices array on javascript side.
+  CAPI(bt_adapter_foreach_bonded_device(OnKnownBondedDevice, obj));
+
+  obj->js_reply_needed_ = false;
+
+  return FALSE;
+}
+
+void BluetoothInstance::InitializeAdapter() {
+  // register C API bluetooth callbacks
+  CAPI(bt_adapter_set_state_changed_cb(OnStateChanged, this));
+  CAPI(bt_adapter_set_name_changed_cb(OnNameChanged, this));
+  CAPI(bt_adapter_set_visibility_mode_changed_cb(OnVisibilityChanged, this));
+  CAPI(bt_adapter_set_device_discovery_state_changed_cb(OnDiscoveryStateChanged,
+                                                        this));
+  CAPI(bt_device_set_bond_created_cb(OnBondCreated, this));
+  CAPI(bt_device_set_bond_destroyed_cb(OnBondDestroyed, this));
+
+  bt_adapter_state_e state = BT_ADAPTER_DISABLED;
+  CAPI(bt_adapter_get_state(&state));
+
+  // Most of the C API functions require as precondition to previously had
+  // called bt_adapter_enable(). So if adapter is turned OFF, we enable it.
+  if (state == BT_ADAPTER_DISABLED) {
+    CAPI(bt_adapter_enable());
+  } else {
+    adapter_enabled_ = true;
+  }
+}
+
+void BluetoothInstance::UninitializeAdapter() {
+  // unregister C API bluetooth callbacks
+  CAPI(bt_adapter_unset_state_changed_cb());
+  CAPI(bt_adapter_unset_name_changed_cb());
+  CAPI(bt_adapter_unset_visibility_mode_changed_cb());
+  CAPI(bt_adapter_unset_device_discovery_state_changed_cb());
+  CAPI(bt_device_unset_bond_created_cb());
+  CAPI(bt_device_unset_bond_destroyed_cb());
+  CAPI(bt_socket_unset_connection_state_changed_cb());
+  CAPI(bt_socket_unset_data_received_cb());
+}
+
+void BluetoothInstance::HandleGetDefaultAdapter(const picojson::value& msg) {
+  if (!adapter_enabled_) {
+    js_reply_needed_ = true;
+    return;
+  }
+
+  GetDefaultAdapter(this);
+}
+
+void BluetoothInstance::HandleSetAdapterProperty(const picojson::value& msg) {
+  picojson::value::object o;
+
+  std::string property = msg.get("property").to_str();
+  callbacks_id_map_[property] = msg.get("reply_id").to_str();
+
+  if (property == "Powered") {
+    bool power = msg.get("value").get<bool>();
+    if (power)
+      CAPI(bt_adapter_enable());
+    else
+      CAPI(bt_adapter_disable());
+  } else if (property == "Name") {
+    std::string name = msg.get("value").to_str();
+    CAPI(bt_adapter_set_name(name.c_str()));
+  } else if (property == "Discoverable") {
+    bool visible = msg.get("value").get<bool>();
+    int timeout = static_cast<int>(msg.get("timeout").get<double>());
+
+    bt_adapter_visibility_mode_e discoverable_mode =
+        BT_ADAPTER_VISIBILITY_MODE_NON_DISCOVERABLE;
+    if (visible) {
+      if (timeout == 0)
+        discoverable_mode = BT_ADAPTER_VISIBILITY_MODE_GENERAL_DISCOVERABLE;
+      else
+        discoverable_mode = BT_ADAPTER_VISIBILITY_MODE_LIMITED_DISCOVERABLE;
+    }
+    CAPI(bt_adapter_set_visibility(discoverable_mode, timeout));
+  } else {
+    LOG_ERR("bad property received!");
+  }
+}
+
+void BluetoothInstance::HandleDiscoverDevices(const picojson::value& msg) {
+  callbacks_id_map_["StartDiscovery"] = msg.get("reply_id").to_str();
+  CAPI(bt_adapter_start_device_discovery());
+}
+
+void BluetoothInstance::HandleStopDiscovery(const picojson::value& msg) {
+  callbacks_id_map_["StopDiscovery"] = msg.get("reply_id").to_str();
+
+  bool is_discovering = false;
+  CAPI(bt_adapter_is_discovering(&is_discovering));
+  if (!is_discovering)
+    return;
+
+  stop_discovery_from_js_ = true;
+  CAPI(bt_adapter_stop_device_discovery());
+}
+
+void BluetoothInstance::HandleCreateBonding(const picojson::value& msg) {
+  callbacks_id_map_["CreateBonding"] = msg.get("reply_id").to_str();
+  std::string address = msg.get("address").to_str();
+  CAPI(bt_device_create_bond(address.c_str()));
+}
+
+void BluetoothInstance::HandleDestroyBonding(const picojson::value& msg) {
+  callbacks_id_map_["DestroyBonding"] = msg.get("reply_id").to_str();
+  std::string address = msg.get("address").to_str();
+  CAPI(bt_device_destroy_bond(address.c_str()));
+}
+
+void BluetoothInstance::HandleRFCOMMListen(const picojson::value& msg) {
+  picojson::value::object o;
+
+  int socket_fd = 0;
+  int error = 0;
+
+  CAPI_ERR(
+      bt_socket_create_rfcomm(msg.get("uuid").to_str().c_str(), &socket_fd),
+      error);
+  if (error) {
+    o["error"] = picojson::value(static_cast<double>(1));
+    InternalPostMessage(picojson::value(o));
+    return;
+  }
+
+  CAPI_ERR(bt_socket_listen_and_accept_rfcomm(socket_fd, 0), error);
+  if (error) {
+    o["error"] = picojson::value(static_cast<double>(1));
+    InternalPostMessage(picojson::value(o));
+    return;
+  }
+
+  CAPI_ERR(bt_socket_set_connection_state_changed_cb(OnSocketConnected, this),
+           error);
+  if (error) {
+    o["error"] = picojson::value(static_cast<double>(1));
+    InternalPostMessage(picojson::value(o));
+    return;
+  }
+
+  o["error"] = picojson::value(static_cast<double>(0));
+  // give the listened socket to JS and store it in service_handler
+  o["socket_fd"] = picojson::value(static_cast<double>(socket_fd));
+  InternalPostMessage(picojson::value(o));
+}
+
+void BluetoothInstance::HandleConnectToService(const picojson::value& msg) {
+  callbacks_id_map_["ConnectToService"] = msg.get("reply_id").to_str();
+  int error = 0;
+
+  CAPI_ERR(
+      bt_socket_connect_rfcomm(msg.get("address").to_str().c_str(),
+                               msg.get("uuid").to_str().c_str()),
+      error);
+  if (!error)
+    CAPI(bt_socket_set_connection_state_changed_cb(OnSocketConnected, this));
+}
+
+void BluetoothInstance::HandleSocketWriteData(const picojson::value& msg) {
+  picojson::value::object o;
+  std::string data = msg.get("data").to_str();
+  int socket = static_cast<int>(msg.get("socket_fd").get<double>());
+
+  CAPI(bt_socket_send_data(socket, data.c_str(),
+                           static_cast<int>(data.size())));
+  o["size"] = picojson::value(static_cast<double>(data.size()));
+
+  InternalSetSyncReply(picojson::value(o));
+}
+
+void BluetoothInstance::HandleCloseSocket(const picojson::value& msg) {
+  picojson::value::object o;
+  int error = 0;
+  int socket = static_cast<int>(msg.get("socket_fd").get<double>());
+
+  CAPI_ERR(bt_socket_disconnect_rfcomm(socket), error);
+  if (!error)
+    o["error"] = picojson::value(static_cast<double>(0));
+  else
+    o["error"] = picojson::value(static_cast<double>(1));
+
+
+  o["cmd"] = picojson::value("");
+  o["reply_id"] = msg.get("reply_id");
+  o["capi"] = picojson::value(static_cast<double>(1));
+  InternalPostMessage(picojson::value(o));
+}
+
+void BluetoothInstance::HandleUnregisterServer(const picojson::value& msg) {
+  callbacks_id_map_["RFCOMMsocketDestroy"] = msg.get("reply_id").to_str();
+  int socket = static_cast<int>(msg.get("server_fd").get<double>());
+
+  CAPI(bt_socket_destroy_rfcomm(socket));
+}
+
+void BluetoothInstance::FlushPendingMessages() {
+  // Flush previous pending messages.
+  if (queue_.empty())
+    return;
+
+  MessageQueue::iterator it;
+  for (it = queue_.begin(); it != queue_.end(); ++it)
+    PostMessage((*it).serialize().c_str());
+}
+
+void BluetoothInstance::InternalPostMessage(picojson::value v) {
+  // If the JavaScript 'context' hasn't been initialized yet (i.e. the C++
+  // backend was loaded and it is already executing but
+  // tizen.bluetooth.getDefaultAdapter() hasn't been called so far), we need to
+  // queue the PostMessage calls and defer them until the default adapter is set
+  // on the JS side. That will guarantee the correct callbacks will be called,
+  // and on the right order, only after tizen.bluetooth.getDefaultAdapter() is
+  // called.
+
+  if (!is_js_context_initialized_) {
+    queue_.push_back(v);
+    return;
+  }
+
+  FlushPendingMessages();
+  PostMessage(v.serialize().c_str());
+}
+
+void BluetoothInstance::InternalSetSyncReply(picojson::value v) {
+  SendSyncReply(v.serialize().c_str());
+
+  FlushPendingMessages();
+}
diff --git a/bluetooth/bluetooth_instance_capi.h b/bluetooth/bluetooth_instance_capi.h
new file mode 100644 (file)
index 0000000..478b04d
--- /dev/null
@@ -0,0 +1,114 @@
+// 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 BLUETOOTH_BLUETOOTH_INSTANCE_CAPI_H_
+#define BLUETOOTH_BLUETOOTH_INSTANCE_CAPI_H_
+
+#include <bluetooth.h>
+#include <glib.h>
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "common/extension.h"
+#include "common/picojson.h"
+
+#define LOG_ERR(msg) std::cerr << "[Error] " << msg << std::endl
+
+// Macros interfacing with C code from Bluetooth API.
+#define CAPI(fnc)                                                              \
+  do {                                                                         \
+    int _er = (fnc);                                                           \
+    if (_er != BT_ERROR_NONE) {                                                \
+      LOG_ERR(#fnc " failed");                                                 \
+    }                                                                          \
+  } while (0)
+
+// same CAPI macro providing error code
+#define CAPI_ERR(fnc, _er)                                                     \
+  do {                                                                         \
+    _er = (fnc);                                                               \
+    if (_er != BT_ERROR_NONE) {                                                \
+      LOG_ERR(#fnc " failed");                                                 \
+    }                                                                          \
+  } while (0)
+
+namespace picojson {
+
+class value;
+
+}  // namespace picojson
+
+class BluetoothInstance : public common::Instance {
+ public:
+  BluetoothInstance();
+  ~BluetoothInstance();
+
+ private:
+  virtual void HandleMessage(const char* msg);
+  virtual void HandleSyncMessage(const char* msg);
+
+  void InitializeAdapter();
+  void UninitializeAdapter();
+
+  void HandleDiscoverDevices(const picojson::value& msg);
+  void HandleStopDiscovery(const picojson::value& msg);
+  void HandleGetDefaultAdapter(const picojson::value& msg);
+  void HandleSetAdapterProperty(const picojson::value& msg);
+  void HandleCreateBonding(const picojson::value& msg);
+  void HandleDestroyBonding(const picojson::value& msg);
+  void HandleRFCOMMListen(const picojson::value& msg);
+  void HandleConnectToService(const picojson::value& msg);
+  void HandleSocketWriteData(const picojson::value& msg);
+  void HandleCloseSocket(const picojson::value& msg);
+  void HandleUnregisterServer(const picojson::value& msg);
+
+  void InternalPostMessage(picojson::value v);
+  void InternalSetSyncReply(picojson::value v);
+  void FlushPendingMessages();
+
+  static gboolean GetDefaultAdapter(gpointer user_data);
+
+  static void OnStateChanged(int result, bt_adapter_state_e adapter_state,
+      void* user_data);
+
+  static void OnNameChanged(char* name, void* user_data);
+
+  static void OnVisibilityChanged(int result,
+      bt_adapter_visibility_mode_e visibility_mode, void* user_data);
+
+  static void OnDiscoveryStateChanged(int result,
+      bt_adapter_device_discovery_state_e discovery_state,
+      bt_adapter_device_discovery_info_s* discovery_info, void* user_data);
+
+  static void OnBondCreated(int result, bt_device_info_s* device_info,
+      void* user_data);
+
+  static void OnBondDestroyed(int result, char* remote_address,
+      void* user_data);
+
+  static bool OnKnownBondedDevice(bt_device_info_s* device_info,
+      void* user_data);
+
+  static void OnSocketConnected(int result,
+      bt_socket_connection_state_e connection_state,
+      bt_socket_connection_s* connection, void* user_data);
+
+  static void OnSocketHasData(bt_socket_received_data_s* data, void* user_data);
+
+  // Map JS reply_id to a C API callback
+  std::map<std::string, std::string> callbacks_id_map_;
+
+  typedef std::vector<picojson::value> MessageQueue;
+  MessageQueue queue_;
+
+  bool is_js_context_initialized_;
+  bool adapter_enabled_;
+  bool js_reply_needed_;
+  bool stop_discovery_from_js_;
+};
+
+#endif  // BLUETOOTH_BLUETOOTH_INSTANCE_CAPI_H_
index 4b13c28..9eebaf5 100644 (file)
@@ -2,14 +2,24 @@
 
 <body>
 
-<input type="text" size=50 id="text">
+<input type="text" size=90 id="text">
 <br>
-<textarea cols=50 rows=20 id="output"></textarea>
+<textarea cols=80 rows=25 id="output"></textarea>
 <br>
 <button id="button1">Click 1</button>
 <button id="button2">Click 2</button>
 <button id="button3">Click 3</button>
 <button id="button4">Click 4</button>
+<button id="button5">Click 5</button>
+<button id="button6">Click 6</button>
+<button id="button7">Click 7</button>
+<button id="button8">Click 8</button>
+<button id="button9">Click 9</button>
+<button id="button10">Click 10</button>
+<button id="button11">Click 11</button>
+<button id="button12">Click 12</button>
+<button id="button13">Click 13</button>
+<button id="button14">Click 14</button>
 
 <pre id="console"></pre>
 <script src="js/js-test-pre.js"></script>
 var output = document.getElementById("output");
 
 var adapter = tizen.bluetooth.getDefaultAdapter();
+output.value += '\n## Default adapter ##';
+output.value += '\nadapter name: ' + adapter.name;
+output.value += ' / address ' + adapter.address;
 
 var devices = [];
 var device_index = 0;
 
 function handle(button, text, callback) {
-    var b = document.getElementById(button);
-    b.innerText = text;
-    b.addEventListener("click", callback);
+  var b = document.getElementById(button);
+  b.innerText = text;
+  b.addEventListener("click", callback);
 }
 
 var oldName = "";
 var oldVisible = false;
+
+var CHAT_SERVICE_UUID = "5BCE9431-6C75-32AB-AFE0-2EC108A30860";
+var chatServiceHandler = null;
+var clientSocket = null;
+
+function validateAddress(address) {
+  if (typeof address !== 'string')
+    return false;
+
+  var regExp = /([\dA-F][\dA-F]:){5}[\dA-F][\dA-F]/i;
+  if (!address.match(regExp))
+    return false;
+
+  return true;
+}
+
+function getAddressFromInputText() {
+  var address = document.getElementById("text");
+
+  if (address.value == "") {
+    output.value += '\nPlease enter a MAC address in input area first !!!';
+    return;
+  }
+
+  if (!validateAddress(address.value))
+    output.value += '\nMAC address is bad !!!';
+
+  return address.value;
+}
+
+function runPowerTest() {
+  oldState = adapter.powered;
+  output.value += '\nAdapter Power : ' + adapter.powered;
+  if(oldState == false)
+    adapter.setPowered(true, onChangedAdapterState, onErrorCallback);
+  else
+    adapter.setPowered(false, onChangedAdapterState, onErrorCallback);
+};
+
+var onChangedAdapterState = function() {
+  output.value += '\nAdapter New State: ' + adapter.powered;
+}
+
+function runNameTest() {
+  output.value += '\nCurrent adapter Name: ' + adapter.name;
+  adapter.setName("FooBar", onChangedAdapterName, onErrorCallback);
+};
+
+var onChangedAdapterName = function() {
+  output.value += '\nNew adapter Name: ' + adapter.name;
+}
+
+var onChangedAdapterVisible = function() {
+  shouldBe("adapter.visible === !oldVisible", "true");
+  oldVisible = adapter.visible;
+}
+
 function runChangePropertiesTest() {
-    oldName = adapter.name;
-    oldVisible = adapter.visible;
+  oldName = adapter.name;
+  oldVisible = adapter.visible;
 
-    debug("Adapter Name (oldName): " + adapter.name);
-    debug("Adapter Visible (oldVisible): " + adapter.visible);
+  output.value += '\nAdapter Name (oldName): ' + adapter.name;
+  output.value += '\nAdapter Visible (oldVisible): ' + adapter.visible;
 
-    shouldBe("adapter.name === oldName", "true");
-    shouldBe("adapter.powered", "true");
-    shouldBe("adapter.visible === oldVisible", "true");
+  shouldBe("adapter.name === oldName", "true");
+  shouldBe("adapter.powered", "true");
+  shouldBe("adapter.visible === oldVisible", "true");
 
-    adapter.setName("FooBigBarrrrr", onChangedAdapterName, onErrorCallback);
-    adapter.setVisible(!oldVisible, onChangedAdapterVisible, onErrorCallback);
+  adapter.setName("FooBigBarrrrr", onChangedAdapterName, onErrorCallback);
+  adapter.setVisible(!oldVisible, onChangedAdapterVisible, onErrorCallback);
 
-    setTimeout(function() {
-        shouldBe("adapter.name === oldName", "false");
-    }, 1000);
+  setTimeout(function() {
+    shouldBe("adapter.name === oldName", "false");
+  }, 1000);
 
-    setTimeout(function() {
-        adapter.setName(oldName, onChangedAdapterName, onErrorCallback);
-        adapter.setVisible(!oldVisible, onChangedAdapterVisible, onErrorCallback);
-    }, 1000);
+  setTimeout(function() {
+    adapter.setName(oldName, onChangedAdapterName, onErrorCallback);
+    adapter.setVisible(!oldVisible, onChangedAdapterVisible, onErrorCallback);
+  }, 1000);
 };
 
 var discoverDevicesSuccessCallback = {
-    onstarted: function() {
-        output.value += '\n\n## Discovery Started ##';
-    },
-    ondevicefound: function(device) {
-        output.value += '\n' + device_index + '- Device Found: ' + device.name;
-        devices[device_index] = device;
-        device_index += 1;
-    },
-    ondevicedisappeared: function(address) {
-        output.value += '\nDevice Disappeared: ' + address;
-    },
-    onfinished: function(devices) {
-        output.value += '\n\n## Discovery Finished. Devices Found: ##';
-        for (var i = 0; i < devices.length; i++) {
-           output.value += '\nDevice Name: ' + devices[i].name;
-        }
+  onstarted: function() {
+    output.value += '\n\n## Discovery Started ##';
+  },
+  ondevicefound: function(device) {
+    output.value += '\n' + device_index + '- Device Found: ' + device.name;
+    devices[device_index] = device;
+    device_index += 1;
+  },
+  ondevicedisappeared: function(address) {
+    output.value += '\nDevice Disappeared: ' + address;
+  },
+  onfinished: function(devices) {
+    output.value += '\n\n## Discovery Finished. Devices Found: ##';
+    for (var i = 0; i < devices.length; i++) {
+      output.value += '\nDevice Name: ' + devices[i].name;
     }
+  }
 };
 
-var onErrorCallback = function(e) {
-    console.log("\nFailed: " + e.message);
+var onGotKnownDevices = function(devices) {
+  output.value += '\n\n## Known Devices: ##';
+  for (var i = 0; i < devices.length; i++) {
+    output.value += '\nDevice Name: ' + devices[i].name;
+    output.value += ' / address: ' + devices[i].address;
+  }
+}
+
+var onGotDeviceInfo = function(device) {
+  output.value += '\n\n## Get Device Info: ##';
+  output.value += '\nName: ' + device.name;
+  output.value += ' / Address: ' + device.address;
+  output.value += '\nClass major: ' + device.deviceClass.major;
+  output.value += ' / minor: ' + device.deviceClass.minor;
+  output.value += ' / service class: ' + device.deviceClass.services;
+  output.value += '\nIs Bonded: ' + ((device.isBonded) ? "Yes" : "No");
+  output.value += ' / Is Trusted: ' + ((device.isTrusted) ? "Yes" : "No");
+  output.value += ' / Is Connected: ' + ((device.isConnected) ? "Yes" : "No");
+  output.value += '\nUUIDs: ' + device.uuids.join("\n");
+ }
+
+var onHasServicesTests = function(device) {
+  output.value += '\n\n## hasServices tests ##';
+  var test = false;
+  test = device.deviceClass.hasService(tizen.bluetooth.deviceService.POSITIONING);
+  output.value += '\nPOSITIONING: ' + (test ? "Yes" : "No");
+  test = device.deviceClass.hasService(tizen.bluetooth.deviceService.NETWORKING);
+  output.value += ' / NETWORKING: ' + (test ? "Yes" : "No");
+  test = device.deviceClass.hasService(tizen.bluetooth.deviceService.TELEPHONY);
+  output.value += ' / TELEPHONY: ' + (test ? "Yes" : "No");
+}
+
+function runCreateBondingTest() {
+  output.value += '\n\n## Create bonding with MAC: ' + getAddressFromInputText() + ' ##';
+  adapter.createBonding(getAddressFromInputText(), onSuccessCreateBonding, onErrorCallback);
 };
 
-var onGotKnownDevices = function(devices) {
-    output.value += '\n\n## Known Devices: ##';
-    for (var i = 0; i < devices.length; i++) {
-       output.value += '\nDevice Name: ' + devices[i].name;
-    }
+var onSuccessCreateBonding = function(device) {
+  output.value += '\nDevice Name:' + device.name + ' bond successfully created !';
+  output.value += '\nDevice Address:' + device.address;
 }
 
-var onChangedAdapterName = function() {
-    debug("Adapter New Name: " + adapter.name);
+function runDestroyBondingTest() {
+  output.value += '\n\n## Destroy bonding with MAC: ' + getAddressFromInputText() + ' ##';
+  adapter.destroyBonding(getAddressFromInputText(), onSuccessDestroyBonding, onErrorCallback);
+
+};
+
+var onSuccessDestroyBonding = function(device) {
+  output.value += '\nDevice Name:' + device.name + ' bond successfully destroyed !';
+  output.value += '\nDevice Address:' + device.address;
 }
 
-var onChangedAdapterVisible = function() {
-    shouldBe("adapter.visible === !oldVisible", "true");
-    oldVisible = adapter.visible;
+var onChatServiceSuccessCb = function(handler) {
+  output.value += '\nChat service registration was successful!';
+
+  chatServiceHandler = handler;
+
+  handler.onconnect = function(socket) {
+    output.value += '\nClient is connected: ' + socket.peer.name + ',' + socket.peer.address;
+    socket.onmessage = function() {
+      var data = socket.readData();
+      output.value += '\nThe socket received data: ' + socket.data;
+    };
+
+    socket.onclose = function() {
+      output.value += '\nThe socket is closed.';
+    };
+  };
 }
 
-handle("button1", "Scan", function() {
-    adapter.discoverDevices(discoverDevicesSuccessCallback, function(e){
-        console.log ("Failed to search devices: " + e.message + "(" + e.name + ")");
+function runUnregisterChatServiceTest() {
+  if (chatServiceHandler != null) {
+    chatServiceHandler.unregister(function() {
+      output.value += '\nChat service is unregistered';
+      chatServiceHandler = null;
+      },
+      function(e) {
+        output.value += '\nFailed to unregister service: ' + e.message;
     });
+  }
+}
+
+var onSocketConnected = function(socket) {
+  clientSocket = socket;
+  output.value += '\nOpening a socket successfully!!!';
+  socket.onmessage = function () {
+    var data = socket.readData();
+    var recvmsg = "";
+    for (var i = 0; i < data.length; i++)  {
+      recvmsg += String.fromCharCode(data[i]);
+    }
+    output.value += '\nserver msg >> ' + recvmsg;
+  };
+
+  socket.onclose = function() {
+    output.value += '\nsocket disconnected.';
+  };
+}
+
+var onDeviceReady = function(device) {
+  if (device.uuids.indexOf(CHAT_SERVICE_UUID) != -1) {
+    device.connectToServiceByUUID(CHAT_SERVICE_UUID, onSocketConnected, function(e) {
+      output.value += '\nError connecting to service. Reason: ' + e.message;
+    });
+  } else {
+    output.value += '\nChat service is not supported by this device';
+  }
+}
+
+function sendMessage(msg) {
+  if (clientSocket != null && clientSocket.state == "OPEN") {
+    clientSocket.writeData(msg);
+  }
+}
+
+var onErrorCallback = function(e) {
+  output.value += '\n[ERROR] Failed: ' + e.message;
+};
+
+handle("button1", "Power On/OFF", function() {
+  runPowerTest();
 });
 
-handle("button2", "Stop Scan", function() {
-    adapter.stopDiscovery(function() {
-        output.value += "\n## Discovery Stopped ##";
-        }, onErrorCallback);
+handle("button2", "Test Name", function() {
+  runNameTest();
 });
 
-handle("button3", "Get Known Devices", function() {
-    adapter.getKnownDevices(onGotKnownDevices, onErrorCallback);
+handle("button3", "Run Adapter properties test", function() {
+  runChangePropertiesTest();
 });
 
-handle("button4", "Run Adapter properties test", function() {
-    runChangePropertiesTest();
+
+handle("button4", "Start Scan", function() {
+  adapter.discoverDevices(discoverDevicesSuccessCallback, function(e){
+    output.value += '\n[ERROR] Failed to search devices: ' + e.message + '(' + e.name + ')';
+  });
+});
+
+handle("button5", "Stop Scan", function() {
+  adapter.stopDiscovery(function() {
+    output.value += "\n## Discovery Stopped ##";
+  }, onErrorCallback);
+});
+
+handle("button6", "Get Known Devices", function() {
+  adapter.getKnownDevices(onGotKnownDevices, onErrorCallback);
+});
+
+handle("button7", "Get Device Info", function() {
+  adapter.getDevice(getAddressFromInputText(), onGotDeviceInfo, onErrorCallback);
+});
+
+handle("button8", "Test hasService", function() {
+  adapter.getDevice(getAddressFromInputText(), onHasServicesTests, onErrorCallback);
+});
+
+handle("button9", "Create Bonding", function() {
+  runCreateBondingTest();
+});
+
+handle("button10", "Destroy Bonding", function() {
+  runDestroyBondingTest();
+});
+
+handle("button11", "Register RFCOMM chat service", function() {
+  output.value += '\n\n## Register RFCOMM Chat service ##';
+  adapter.registerRFCOMMServiceByUUID(CHAT_SERVICE_UUID, "Chat service", onChatServiceSuccessCb, onErrorCallback);
+});
+
+handle("button12", "Unregister chat service", function() {
+  runUnregisterChatServiceTest();
+});
+
+handle("button13", "Connect to chat service", function() {
+  output.value += '\n\n## Connect to Chat service ##';
+  adapter.getDevice(getAddressFromInputText(), onDeviceReady, function(e) { output.value += '\n"[ERROR] ' + e.message; });
+});
+
+handle("button14", "Send message", function() {
+  var msg = document.getElementById("text");
+  var message = msg.value;
+  output.value += '\n\n## Send message ##';
+
+  if (message == "") {
+    output.value += '\nPlease write a message in text area first !';
+    return;
+  }
+
+  if (typeof message !== 'string') {
+    output.value += '\ntext message is invalid !';
+    return;
+  }
+
+  sendMessage(message);
 });
 </script>