5861917209e8d181a2e47731501baf9f2e4fe53e
[platform/framework/web/tizen-extensions-crosswalk.git] / bluetooth / bluetooth_api.js
1 // Copyright (c) 2013 Intel Corporation. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 var _callbacks = {};
6 var _next_reply_id = 0;
7
8 var postMessage = function(msg, callback) {
9   var reply_id = _next_reply_id;
10   _next_reply_id += 1;
11   _callbacks[reply_id] = callback;
12   msg.reply_id = reply_id.toString();
13   extension.postMessage(JSON.stringify(msg));
14 };
15
16 extension.setMessageListener(function(json) {
17   var msg = JSON.parse(json);
18   if (msg.cmd == 'BondedDevice')
19     handleBondedDevice(msg);
20   else if (msg.cmd == 'DeviceFound')
21     handleDeviceFound(msg);
22   else if (msg.cmd == 'DiscoveryFinished')
23     handleDiscoveryFinished();
24   else if (msg.cmd == 'DeviceRemoved')
25     handleDeviceRemoved(msg.Address);
26   else if (msg.cmd == 'DeviceUpdated')
27     handleDeviceUpdated(msg);
28   else if (msg.cmd == 'AdapterUpdated')
29     handleAdapterUpdated(msg);
30   else if (msg.cmd == 'RFCOMMSocketAccept')
31     handleRFCOMMSocketAccept(msg);
32   else if (msg.cmd == 'SocketHasData')
33     handleSocketHasData(msg);
34   else if (msg.cmd == 'SocketClosed')
35     handleSocketClosed(msg);
36   else { // Then we are dealing with postMessage return.
37     var reply_id = msg.reply_id;
38     var callback = _callbacks[reply_id];
39     if (callback) {
40       delete msg.reply_id;
41       delete _callbacks[reply_id];
42       callback(msg);
43     } else {
44       // do not print error log when the postmessage was not initiated by JS
45       if (reply_id != '')
46         console.log('Invalid reply_id from Tizen Bluetooth: ' + reply_id);
47     }
48   }
49 });
50
51 function Adapter() {
52   this.found_devices = []; // Filled while a Discovering.
53   this.known_devices = []; // Keeps Managed and Found devices.
54   this.discovery_callbacks = {};
55   this.isReady = false;
56   this.service_handlers = [];
57   this.sockets = [];
58   this.change_listener = null;
59 }
60
61 function validateAddress(address) {
62   if (typeof address !== 'string')
63     return false;
64
65   var regExp = /([\dA-F][\dA-F]:){5}[\dA-F][\dA-F]/i;
66
67   if (!address.match(regExp))
68     return false;
69
70   return true;
71 }
72
73 Adapter.prototype.checkServiceAvailability = function(errorCallback) {
74   if (adapter.isReady && defaultAdapter.powered)
75     return false;
76
77   if (errorCallback) {
78     var error = new tizen.WebAPIError(tizen.WebAPIException.SERVICE_NOT_AVAILABLE_ERR);
79     errorCallback(error);
80   }
81
82   return true;
83 };
84
85 Adapter.prototype.indexOfDevice = function(devices, address) {
86   for (var i = 0; i < devices.length; i++) {
87     if (devices[i].address == address)
88       return i;
89   }
90   return -1;
91 };
92
93 Adapter.prototype.addDevice = function(device, on_discovery) {
94   var new_device = false;
95
96   if (on_discovery) {
97     var index = this.indexOfDevice(this.found_devices, device.address);
98     if (index == -1) {
99       this.found_devices.push(device);
100       new_device = true;
101     } else {
102       this.found_devices[index] = device;
103       new_device = false;
104     }
105   }
106
107   var i = this.indexOfDevice(this.known_devices, device.address);
108   if (i == -1)
109     this.known_devices.push(device);
110   else
111     this.known_devices[i] = device;
112
113   return new_device;
114 };
115
116 Adapter.prototype.updateDevice = function(device) {
117   var index = this.indexOfDevice(this.known_devices, device.address);
118   if (index == -1)
119     this.known_devices.push(device);
120   else
121     this.known_devices[index]._updateProperties(device);
122 };
123
124 // This holds the adapter the Bluetooth backend is currently using.
125 // In BlueZ 4, for instance, this would represent the "default adapter".
126 // BlueZ 5 has no such concept, so this will hold the currently available
127 // adapter, which can be just the first one found.
128 var adapter = new Adapter();
129
130 var deepCopyDevices = function(devices) {
131   var copiedDevices = [];
132   for (var i = 0; i < devices.length; i++)
133     copiedDevices[i] = devices[i]._clone();
134
135   return copiedDevices;
136 };
137
138 var handleBondedDevice = function(msg) {
139   var device = new BluetoothDevice(msg);
140   adapter.addDevice(device, false);
141 };
142
143 var handleDeviceFound = function(msg) {
144   var device = new BluetoothDevice(msg);
145   var is_new = adapter.addDevice(device, msg.found_on_discovery);
146
147   // FIXME(jeez): we are not returning a deep copy so we can keep
148   // the devices up-to-date. We have to find a better way to handle this.
149   if (is_new && msg.found_on_discovery && adapter.discovery_callbacks.ondevicefound)
150     adapter.discovery_callbacks.ondevicefound(device);
151 };
152
153 var handleDiscoveryFinished = function() {
154   // FIXME(jeez): we are not returning a deep copy so we can keep
155   // the devices up-to-date. We have to find a better way to handle this.
156   if (typeof adapter.discovery_callbacks.onfinished === 'function')
157     adapter.discovery_callbacks.onfinished(adapter.found_devices);
158
159   adapter.found_devices = [];
160   adapter.discovery_callbacks = {};
161 };
162
163 var handleDeviceRemoved = function(address) {
164   var foundDevices = adapter.found_devices;
165   var knownDevices = adapter.known_devices;
166
167   for (var i = 0; i < foundDevices.length; i++) {
168     if (foundDevices[i].address === address) {
169       foundDevices.splice(i, 1);
170       break;
171     }
172   }
173
174   for (var i = 0; i < knownDevices.length; i++) {
175     if (knownDevices[i].address === address) {
176       knownDevices.splice(i, 1);
177       break;
178     }
179   }
180 };
181
182 var handleDeviceUpdated = function(msg) {
183   var device = new BluetoothDevice(msg);
184   adapter.updateDevice(device);
185 };
186
187 var handleAdapterUpdated = function(msg) {
188   var listener = adapter.change_listener;
189
190   if (msg.Name) {
191     _addConstProperty(defaultAdapter, 'name', msg.Name);
192     if (listener && listener.onnamechanged) {
193       adapter.change_listener.onnamechanged(msg.Name);
194     }
195   }
196
197   if (msg.Address)
198     _addConstProperty(defaultAdapter, 'address', msg.Address);
199
200   if (msg.Powered) {
201     var powered = (msg.Powered === 'true') ? true : false;
202     _addConstProperty(defaultAdapter, 'powered', powered);
203     if (defaultAdapter.powered !== powered && listener && listener.onstatechanged) {
204       adapter.change_listener.onstatechanged(powered);
205     }
206   }
207
208   if (msg.Discoverable) {
209     var visibility = (msg.Discoverable === 'true') ? true : false;
210
211     if (defaultAdapter.visible !== visibility && listener && listener.onvisibilitychanged) {
212       adapter.change_listener.onvisibilitychanged(visibility);
213     }
214     _addConstProperty(defaultAdapter, 'visible', visibility);
215   }
216
217   defaultAdapter.isReady = true;
218 };
219
220 var handleRFCOMMSocketAccept = function(msg) {
221   for (var i in adapter.service_handlers) {
222     var server = adapter.service_handlers[i];
223     // FIXME(clecou) BlueZ4 backend compares rfcomm channel number but this parameter
224     // is not available in Tizen C API so we check socket fd.
225     // A better approach would be to adapt backends instances to have a single JSON protocol.
226     if (server.channel === msg.channel || server.server_fd === msg.socket_fd) {
227       var j = adapter.indexOfDevice(adapter.known_devices, msg.peer);
228       var peer = adapter.known_devices[j];
229
230       var socket = new BluetoothSocket(server.uuid, peer, msg);
231
232       adapter.sockets.push(socket);
233
234       _addConstProperty(server, 'isConnected', true);
235
236       if (server.onconnect && typeof server.onconnect === 'function')
237         server.onconnect(socket);
238       return;
239     }
240   }
241 };
242
243 var handleSocketHasData = function(msg) {
244   for (var i in adapter.sockets) {
245     var socket = adapter.sockets[i];
246     if (socket.socket_fd === msg.socket_fd) {
247       socket.data = msg.data;
248
249       if (socket.onmessage && typeof socket.onmessage === 'function')
250         socket.onmessage();
251
252       socket.data = [];
253       return;
254     }
255   }
256 };
257
258 var handleSocketClosed = function(msg) {
259   for (var i in adapter.sockets) {
260     var socket = adapter.sockets[i];
261     if (socket.socket_fd === msg.socket_fd) {
262       if (socket.onclose && typeof socket.onmessage === 'function')
263         socket.onclose();
264
265       _addConstProperty(socket, 'isConnected', false);
266       return;
267     }
268   }
269 };
270
271 var defaultAdapter = new BluetoothAdapter();
272
273 exports.getDefaultAdapter = function() {
274   var msg = {
275     'cmd': 'GetDefaultAdapter'
276   };
277   var result = JSON.parse(extension.internal.sendSyncMessage(JSON.stringify(msg)));
278
279   if (!result.error) {
280     _addConstProperty(defaultAdapter, 'name', result.name);
281     _addConstProperty(defaultAdapter, 'address', result.address);
282     _addConstProperty(defaultAdapter, 'powered', result.powered);
283     _addConstProperty(defaultAdapter, 'visible', result.visible);
284
285     if (result.hasOwnProperty('address') && result.address != '')
286       adapter.isReady = true;
287   } else {
288     adapter.isReady = false;
289     throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
290   }
291
292   return defaultAdapter;
293 };
294
295 exports.deviceMajor = {};
296 var deviceMajor = {
297   'MISC': { value: 0x00, configurable: false, writable: false },
298   'COMPUTER': { value: 0x01, configurable: false, writable: false },
299   'PHONE': { value: 0x02, configurable: false, writable: false },
300   'NETWORK': { value: 0x03, configurable: false, writable: false },
301   'AUDIO_VIDEO': { value: 0x04, configurable: false, writable: false },
302   'PERIPHERAL': { value: 0x05, configurable: false, writable: false },
303   'IMAGING': { value: 0x06, configurable: false, writable: false },
304   'WEARABLE': { value: 0x07, configurable: false, writable: false },
305   'TOY': { value: 0x08, configurable: false, writable: false },
306   'HEALTH': { value: 0x09, configurable: false, writable: false },
307   'UNCATEGORIZED': { value: 0x1F, configurable: false, writable: false }
308 };
309 Object.defineProperties(exports.deviceMajor, deviceMajor);
310
311 exports.deviceMinor = {};
312 var deviceMinor = {
313   'COMPUTER_UNCATEGORIZED': { value: 0x00, configurable: false, writable: false },
314   'COMPUTER_DESKTOP': { value: 0x01, configurable: false, writable: false },
315   'COMPUTER_SERVER': { value: 0x02, configurable: false, writable: false },
316   'COMPUTER_LAPTOP': { value: 0x03, configurable: false, writable: false },
317   'COMPUTER_HANDHELD_PC_OR_PDA': { value: 0x04, configurable: false, writable: false },
318   'COMPUTER_PALM_PC_OR_PDA': { value: 0x05, configurable: false, writable: false },
319   'COMPUTER_WEARABLE': { value: 0x06, configurable: false, writable: false },
320   'PHONE_UNCATEGORIZED': { value: 0x00, configurable: false, writable: false },
321   'PHONE_CELLULAR': { value: 0x01, configurable: false, writable: false },
322   'PHONE_CORDLESS': { value: 0x02, configurable: false, writable: false },
323   'PHONE_SMARTPHONE': { value: 0x03, configurable: false, writable: false },
324   'PHONE_MODEM_OR_GATEWAY': { value: 0x04, configurable: false, writable: false },
325   'PHONE_ISDN': { value: 0x05, configurable: false, writable: false },
326   'AV_UNRECOGNIZED': { value: 0x00, configurable: false, writable: false },
327   'AV_WEARABLE_HEADSET': { value: 0x01, configurable: false, writable: false },
328   'AV_HANDSFREE': { value: 0x02, configurable: false, writable: false },
329   'AV_MICROPHONE': { value: 0x04, configurable: false, writable: false },
330   'AV_LOUDSPEAKER': { value: 0x05, configurable: false, writable: false },
331   'AV_HEADPHONES': { value: 0x06, configurable: false, writable: false },
332   'AV_PORTABLE_AUDIO': { value: 0x07, configurable: false, writable: false },
333   'AV_CAR_AUDIO': { value: 0x08, configurable: false, writable: false },
334   'AV_SETTOP_BOX': { value: 0x09, configurable: false, writable: false },
335   'AV_HIFI': { value: 0x0a, configurable: false, writable: false },
336   'AV_VCR': { value: 0x0b, configurable: false, writable: false },
337   'AV_VIDEO_CAMERA': { value: 0x0c, configurable: false, writable: false },
338   'AV_CAMCORDER': { value: 0x0d, configurable: false, writable: false },
339   'AV_MONITOR': { value: 0x0e, configurable: false, writable: false },
340   'AV_DISPLAY_AND_LOUDSPEAKER': { value: 0x0f, configurable: false, writable: false },
341   'AV_VIDEO_CONFERENCING': { value: 0x10, configurable: false, writable: false },
342   'AV_GAMING_TOY': { value: 0x12, configurable: false, writable: false },
343   'PERIPHERAL_UNCATEGORIZED': { value: 0, configurable: false, writable: false },
344   'PERIPHERAL_KEYBOARD': { value: 0x10, configurable: false, writable: false },
345   'PERIPHERAL_POINTING_DEVICE': { value: 0x20, configurable: false, writable: false },
346   'PERIPHERAL_KEYBOARD_AND_POINTING_DEVICE': { value: 0x30, configurable: false, writable: false },
347   'PERIPHERAL_JOYSTICK': { value: 0x01, configurable: false, writable: false },
348   'PERIPHERAL_GAMEPAD': { value: 0x02, configurable: false, writable: false },
349   'PERIPHERAL_REMOTE_CONTROL': { value: 0x03, configurable: false, writable: false },
350   'PERIPHERAL_SENSING_DEVICE': { value: 0x04, configurable: false, writable: false },
351   'PERIPHERAL_DEGITIZER_TABLET': { value: 0x05, configurable: false, writable: false },
352   'PERIPHERAL_CARD_READER': { value: 0x06, configurable: false, writable: false },
353   'PERIPHERAL_DIGITAL_PEN': { value: 0x07, configurable: false, writable: false },
354   'PERIPHERAL_HANDHELD_SCANNER': { value: 0x08, configurable: false, writable: false },
355   'PERIPHERAL_HANDHELD_INPUT_DEVICE': { value: 0x09, configurable: false, writable: false },
356   'IMAGING_UNCATEGORIZED': { value: 0x00, configurable: false, writable: false },
357   'IMAGING_DISPLAY': { value: 0x04, configurable: false, writable: false },
358   'IMAGING_CAMERA': { value: 0x08, configurable: false, writable: false },
359   'IMAGING_SCANNER': { value: 0x10, configurable: false, writable: false },
360   'IMAGING_PRINTER': { value: 0x20, configurable: false, writable: false },
361   'WEARABLE_WRITST_WATCH': { value: 0x01, configurable: false, writable: false },
362   'WEARABLE_PAGER': { value: 0x02, configurable: false, writable: false },
363   'WEARABLE_JACKET': { value: 0x03, configurable: false, writable: false },
364   'WEARABLE_HELMET': { value: 0x04, configurable: false, writable: false },
365   'WEARABLE_GLASSES': { value: 0x05, configurable: false, writable: false },
366   'TOY_ROBOT': { value: 0x01, configurable: false, writable: false },
367   'TOY_VEHICLE': { value: 0x02, configurable: false, writable: false },
368   'TOY_DOLL': { value: 0x03, configurable: false, writable: false },
369   'TOY_CONTROLLER': { value: 0x04, configurable: false, writable: false },
370   'TOY_GAME': { value: 0x05, configurable: false, writable: false },
371   'HEALTH_UNDEFINED': { value: 0x00, configurable: false, writable: false },
372   'HEALTH_BLOOD_PRESSURE_MONITOR': { value: 0x01, configurable: false, writable: false },
373   'HEALTH_THERMOMETER': { value: 0x02, configurable: false, writable: false },
374   'HEALTH_WEIGHING_SCALE': { value: 0x03, configurable: false, writable: false },
375   'HEALTH_GLUCOSE_METER': { value: 0x04, configurable: false, writable: false },
376   'HEALTH_PULSE_OXIMETER': { value: 0x05, configurable: false, writable: false },
377   'HEALTH_PULSE_RATE_MONITOR': { value: 0x06, configurable: false, writable: false },
378   'HEALTH_DATA_DISPLAY': { value: 0x07, configurable: false, writable: false },
379   'HEALTH_STEP_COUNTER': { value: 0x08, configurable: false, writable: false },
380   'HEALTH_BODY_COMPOSITION_ANALYZER': { value: 0x09, configurable: false, writable: false },
381   'HEALTH_PEAK_FLOW_MONITOR': { value: 0x0a, configurable: false, writable: false },
382   'HEALTH_MEDICATION_MONITOR': { value: 0x0b, configurable: false, writable: false },
383   'HEALTH_KNEE_PROSTHESIS': { value: 0x0c, configurable: false, writable: false },
384   'HEALTH_ANKLE_PROSTHESIS': { value: 0x0d, configurable: false, writable: false }
385 };
386 Object.defineProperties(exports.deviceMinor, deviceMinor);
387
388 exports.deviceService = {};
389 var deviceService = {
390   'LIMITED_DISCOVERABILITY': { value: 0x0001, configurable: false, writable: false },
391   'POSITIONING': { value: 0x0008, configurable: false, writable: false },
392   'NETWORKING': { value: 0x0010, configurable: false, writable: false },
393   'RENDERING': { value: 0x0020, configurable: false, writable: false },
394   'CAPTURING': { value: 0x0040, configurable: false, writable: false },
395   'OBJECT_TRANSFER': { value: 0x0080, configurable: false, writable: false },
396   'AUDIO': { value: 0x0100, configurable: false, writable: false },
397   'TELEPHONY': { value: 0x0200, configurable: false, writable: false },
398   'INFORMATION': { value: 0x0400, configurable: false, writable: false }
399 };
400 Object.defineProperties(exports.deviceService, deviceService);
401
402 function _addConstProperty(obj, propertyKey, propertyValue) {
403   Object.defineProperty(obj, propertyKey, {
404     configurable: true,
405     writable: false,
406     value: propertyValue
407   });
408 }
409
410 function BluetoothAdapter() {
411   _addConstProperty(this, 'name', '');
412   _addConstProperty(this, 'address', '00:00:00:00:00:00');
413   _addConstProperty(this, 'powered', false);
414   _addConstProperty(this, 'visible', false);
415 }
416
417 BluetoothAdapter.prototype.setName = function(name, successCallback, errorCallback) {
418   if (!xwalk.utils.validateArguments('s?ff', arguments)) {
419     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
420   }
421
422   if (adapter.checkServiceAvailability(errorCallback))
423     return;
424
425   var msg = {
426     'cmd': 'SetAdapterProperty',
427     'property': 'Name',
428     'value': name
429   };
430
431   postMessage(msg, function(result) {
432     if (result.error != 0) {
433       if (errorCallback) {
434         var error = new tizen.WebAPIError(tizen.WebAPIException.INVALID_VALUES_ERR);
435         errorCallback(error);
436       }
437
438       throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
439       return;
440     }
441
442     _addConstProperty(defaultAdapter, 'name', name);
443
444     if (successCallback)
445       successCallback();
446   });
447 };
448
449 BluetoothAdapter.prototype.setPowered = function(state, successCallback, errorCallback) {
450   if (!xwalk.utils.validateArguments('b?ff', arguments)) {
451     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
452   }
453
454   if ((state === defaultAdapter.powered) && successCallback) {
455     successCallback();
456     return;
457   }
458
459   var msg = {
460     'cmd': 'SetAdapterProperty',
461     'property': 'Powered',
462     'value': state
463   };
464
465   postMessage(msg, function(result) {
466     if (result.error != 0) {
467       if (errorCallback) {
468         var error = new tizen.WebAPIError(tizen.WebAPIException.INVALID_VALUES_ERR);
469         errorCallback(error);
470         return;
471       }
472
473       throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
474     }
475
476     _addConstProperty(defaultAdapter, 'powered', state);
477
478     if (successCallback)
479       successCallback();
480   });
481 };
482
483 BluetoothAdapter.prototype.setVisible = function(mode, successCallback, errorCallback, timeout) {
484   if (!xwalk.utils.validateArguments('b?ffn', arguments)) {
485     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
486   }
487
488   if (adapter.checkServiceAvailability(errorCallback))
489     return;
490
491   if (timeout === undefined || typeof timeout !== 'number' || timeout < 0)
492     timeout = 180; // According to tizen.bluetooth documentation.
493
494   var msg = {
495     'cmd': 'SetAdapterProperty',
496     'property': 'Discoverable',
497     'value': mode,
498     'timeout': timeout
499   };
500
501   postMessage(msg, function(result) {
502     if (result.error != 0) {
503       if (errorCallback) {
504         var error = new tizen.WebAPIError(tizen.WebAPIException.INVALID_VALUES_ERR);
505         errorCallback(error);
506       }
507
508       throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
509       return;
510     }
511
512     _addConstProperty(defaultAdapter, 'visible', mode);
513
514     if (successCallback)
515       successCallback();
516   });
517 };
518
519 BluetoothAdapter.prototype.discoverDevices = function(discoverySuccessCallback, errorCallback) {
520   if (!xwalk.utils.validateArguments('o?f', arguments)) {
521     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
522   }
523
524   if (!xwalk.utils.validateObject(discoverySuccessCallback, 'ffff',
525       ['onstarted', 'ondevicefound', 'ondevicedisappeared', 'onfinished'])) {
526     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
527   }
528
529   if (adapter.checkServiceAvailability(errorCallback))
530     return;
531
532   var msg = {
533     'cmd': 'DiscoverDevices'
534   };
535   postMessage(msg, function(result) {
536     if (result.error != 0) {
537       if (errorCallback) {
538         var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
539         errorCallback(error);
540       }
541       return;
542     }
543
544     adapter.discovery_callbacks = discoverySuccessCallback;
545
546     if (discoverySuccessCallback && discoverySuccessCallback.onstarted)
547       discoverySuccessCallback.onstarted();
548   });
549 };
550
551 BluetoothAdapter.prototype.stopDiscovery = function(successCallback, errorCallback) {
552   if (!xwalk.utils.validateArguments('?ff', arguments)) {
553     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
554   }
555
556   if (adapter.checkServiceAvailability(errorCallback))
557     return;
558
559   var msg = {
560     'cmd': 'StopDiscovery'
561   };
562   postMessage(msg, function(result) {
563     if (result.error != 0) {
564       if (errorCallback) {
565         var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
566         errorCallback(error);
567       }
568       return;
569     }
570
571     if (successCallback)
572       successCallback();
573
574     handleDiscoveryFinished();
575   });
576 };
577
578 BluetoothAdapter.prototype.getKnownDevices = function(deviceArraySuccessCallback, errorCallback) {
579   if (!xwalk.utils.validateArguments('f?f', arguments)) {
580     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
581   }
582
583   if (adapter.checkServiceAvailability(errorCallback))
584     return;
585
586   // FIXME(jeez): we are not returning a deep copy so we can keep
587   // the devices up-to-date. We have to find a better way to handle this.
588   deviceArraySuccessCallback(adapter.known_devices);
589 };
590
591 BluetoothAdapter.prototype.getDevice = function(address, deviceSuccessCallback, errorCallback) {
592   if (!xwalk.utils.validateArguments('sf?f', arguments)) {
593     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
594   }
595
596   if (!validateAddress(address)) {
597     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
598   }
599
600   if (adapter.checkServiceAvailability(errorCallback))
601     return;
602
603   var index = adapter.indexOfDevice(adapter.known_devices, address);
604   if (index == -1) {
605     var error = new tizen.WebAPIError(tizen.WebAPIException.NOT_FOUND_ERR);
606     errorCallback(error);
607     return;
608   }
609
610   deviceSuccessCallback(adapter.known_devices[index]);
611 };
612
613 BluetoothAdapter.prototype.createBonding = function(address, successCallback, errorCallback) {
614   if (!xwalk.utils.validateArguments('sf?f', arguments)) {
615     throw new tizen.WebAPIError(tizen.WebAPIException.TYPE_MISMATCH_ERR);
616   }
617
618   if (!validateAddress(address)) {
619     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
620   }
621
622   if (adapter.checkServiceAvailability(errorCallback))
623     return;
624
625   var msg = {
626     'cmd': 'CreateBonding',
627     'address': address
628   };
629
630   postMessage(msg, function(result) {
631     var cb_device;
632
633     if (result.error != 0) {
634       if (errorCallback) {
635         var error = new tizen.WebAPIError(tizen.WebAPIException.INVALID_VALUES_ERR);
636         errorCallback(error);
637       }
638
639       throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
640       return;
641     }
642
643     if (successCallback) {
644       var known_devices = adapter.known_devices;
645       for (var i = 0; i < known_devices.length; i++) {
646         if (known_devices[i].address === address) {
647           cb_device = known_devices[i];
648           break;
649         }
650       }
651
652       // FIXME(clecou) Update known device state here when using C API Tizen backend
653       // BlueZ backends update the device state automatically when catching dbus signals.
654       // A better approach would be to adapt backends instances to have a single JSON protocol.
655       if (result.capi)
656         _addConstProperty(adapter.known_devices[i], 'isBonded', true);
657
658       successCallback(cb_device);
659     }
660   });
661 };
662
663 BluetoothAdapter.prototype.destroyBonding = function(address, successCallback, errorCallback) {
664   if (!xwalk.utils.validateArguments('s?ff', arguments)) {
665     throw new tizen.WebAPIError(tizen.WebAPIException.TYPE_MISMATCH_ERR);
666   }
667
668   if (!validateAddress(address)) {
669     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
670   }
671
672   if (adapter.checkServiceAvailability(errorCallback))
673     return;
674
675   var msg = {
676     'cmd': 'DestroyBonding',
677     'address': address
678   };
679
680   postMessage(msg, function(result) {
681     var cb_device;
682
683     if (result.error != 0) {
684       if (errorCallback) {
685         var error_type = (result.error == 1) ?
686             tizen.WebAPIException.NOT_FOUND_ERR : tizen.WebAPIException.UNKNOWN_ERR;
687         var error = new tizen.WebAPIError(error_type);
688         errorCallback(error);
689       }
690
691       throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
692       return;
693     }
694
695     if (successCallback) {
696       var known_devices = adapter.known_devices;
697       for (var i = 0; i < known_devices.length; i++) {
698         if (known_devices[i].address === address) {
699           cb_device = known_devices[i];
700           break;
701         }
702       }
703
704       // FIXME(clecou) Update known device state here when using C API Tizen backend
705       // BlueZ backends update the device state automatically when catching dbus signals
706       // A better approach would be to adapt backends instances to have a single JSON protocol.
707       if (result.capi)
708         _addConstProperty(adapter.known_devices[i], 'isBonded', false);
709
710       successCallback(cb_device);
711     }
712   });
713 };
714
715 BluetoothAdapter.prototype.registerRFCOMMServiceByUUID =
716     function(uuid, name, serviceSuccessCallback, errorCallback) {
717   if (!xwalk.utils.validateArguments('ssf?f', arguments)) {
718     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
719   }
720
721   if (adapter.checkServiceAvailability(errorCallback))
722     return;
723
724   var msg = {
725     'cmd': 'RFCOMMListen',
726     'uuid': uuid,
727     'name': name
728   };
729
730   postMessage(msg, function(result) {
731     if (result.error != 0) {
732       if (errorCallback) {
733         var error;
734         if (result.error == 1)
735           error = new tizen.WebAPIError(tizen.WebAPIException.NOT_FOUND_ERR);
736         else
737           error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
738         errorCallback(error);
739       }
740       return;
741     }
742
743     var service = new BluetoothServiceHandler(uuid, name, result);
744     adapter.service_handlers.push(service);
745
746     if (serviceSuccessCallback) {
747       serviceSuccessCallback(service);
748     }
749   });
750 };
751
752 BluetoothAdapter.prototype.setChangeListener = function(listener) {
753   if (!xwalk.utils.validateArguments('o', arguments)) {
754     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
755   }
756
757   if (!xwalk.utils.validateObject(listener, 'fff',
758       ['onstatechanged', 'onnamechanged', 'onvisibilitychanged'])) {
759     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
760   }
761
762   adapter.change_listener = listener;
763 };
764
765 BluetoothAdapter.prototype.unsetChangeListener = function() {
766   adapter.change_listener = null;
767 };
768
769 var _deviceClassMask = {
770   'MINOR': 0x3F,
771   'MAJOR': 0x1F,
772   'SERVICE': 0x7F9
773 };
774
775 function BluetoothDevice(msg) {
776   if (!msg) {
777     _addConstProperty(this, 'name', '');
778     _addConstProperty(this, 'address', '');
779     _addConstProperty(this, 'deviceClass', new BluetoothClass());
780     _addConstProperty(this, 'isBonded', false);
781     _addConstProperty(this, 'isTrusted', false);
782     _addConstProperty(this, 'isConnected', false);
783     _addConstProperty(this, 'uuids', []);
784     return;
785   }
786
787   _addConstProperty(this, 'name', msg.Alias);
788   _addConstProperty(this, 'address', msg.Address);
789
790   _addConstProperty(this, 'deviceClass', new BluetoothClass());
791   _addConstProperty(this.deviceClass, 'minor', (msg.ClassMinor >> 2) & _deviceClassMask.MINOR);
792   _addConstProperty(this.deviceClass, 'major', msg.ClassMajor & _deviceClassMask.MAJOR);
793
794   _addConstProperty(this, 'isBonded', (msg.Paired == 'true') ? true : false);
795   _addConstProperty(this, 'isTrusted', (msg.Trusted == 'true') ? true : false);
796   _addConstProperty(this, 'isConnected', (msg.Connected == 'true') ? true : false);
797
798   if (msg.UUIDs) {
799     if (typeof msg.UUIDs === 'string') {
800       // FIXME(clecou) BlueZ backend sends a string to convert it into an array
801       // A better approach would be to adapt backends instances to have a single JSON protocol.
802       var uuids_array = [];
803       uuids_array = msg.UUIDs.substring(msg.UUIDs.indexOf('[') + 1,
804           msg.UUIDs.indexOf(']')).split(',');
805       for (var i = 0; i < uuids_array.length; i++) {
806         uuids_array[i] = uuids_array[i].substring(2, uuids_array[i].length - 1);
807       }
808       _addConstProperty(this, 'uuids', uuids_array);
809     } else {
810       // Tizen C API backend directly sends an array
811       _addConstProperty(this, 'uuids', msg.UUIDs);
812     }
813   }
814
815   var services = (msg.ClassService >> 13) & _deviceClassMask.SERVICE;
816   var services_array = [];
817
818   // 11 is the number of bits in _deviceClassMask.SERVICE
819   for (var i = 0; i < 11; i++)
820     if ((services & (1 << i)) !== 0)
821       services_array.push(1 << i);
822
823     _addConstProperty(this.deviceClass, 'services', services_array);
824 }
825
826 BluetoothDevice.prototype.connectToServiceByUUID =
827     function(uuid, socketSuccessCallback, errorCallback) {
828
829   if (!xwalk.utils.validateArguments('sf?f', arguments)) {
830     throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
831   }
832
833   if (adapter.checkServiceAvailability(errorCallback))
834     return;
835
836   var uuid_found = false;
837   for (var i = 0; i < this.uuids.length; i++) {
838     if (this.uuids[i] == uuid) {
839       uuid_found = true;
840       break;
841     }
842   }
843   if (uuid_found == false) {
844     var error = new tizen.WebAPIError(tizen.WebAPIException.NOT_FOUND_ERR);
845     errorCallback(error);
846   }
847
848   var msg = {
849     'cmd': 'ConnectToService',
850     'uuid': uuid,
851     'address' : this.address
852   };
853
854   postMessage(msg, function(result) {
855     if (result.error != 0) {
856       if (errorCallback) {
857         var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
858         errorCallback(error);
859       }
860
861       throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
862       return;
863     }
864
865     if (socketSuccessCallback) {
866       var socket_cb = new BluetoothSocket(result.uuid, this, result);
867       socketSuccessCallback(socket_cb);
868     }
869   });
870 };
871
872 BluetoothDevice.prototype._clone = function() {
873   var clone = new BluetoothDevice();
874   _addConstProperty(clone, 'name', this.name);
875   _addConstProperty(clone, 'address', this.address);
876   _addConstProperty(clone, 'deviceClass', this.deviceClass);
877   _addConstProperty(clone, 'isBonded', this.isBonded);
878   _addConstProperty(clone, 'isTrusted', this.isTrusted);
879   _addConstProperty(clone, 'isConnected', this.isConnected);
880
881   var uuids_array = [];
882   for (var i = 0; i < this.uuids.length; i++)
883     uuids_array[i] = this.uuids[i];
884
885   _addConstProperty(clone, 'uuids', uuids_array);
886
887   return clone;
888 };
889
890 BluetoothDevice.prototype._updateProperties = function(device) {
891   if (device.hasOwnProperty('name'))
892     _addConstProperty(this, 'name', device.name);
893   if (device.hasOwnProperty('address'))
894     _addConstProperty(this, 'address', device.address);
895   if (device.hasOwnProperty('deviceClass'))
896     _addConstProperty(this, 'deviceClass', device.deviceClass);
897   if (device.hasOwnProperty('isBonded'))
898     _addConstProperty(this, 'isBonded', device.isBonded);
899   if (device.hasOwnProperty('isTrusted'))
900     _addConstProperty(this, 'isTrusted', device.isTrusted);
901   if (device.hasOwnProperty('isConnected'))
902     _addConstProperty(this, 'isConnected', device.isConnected);
903
904   if (device.hasOwnProperty('uuids')) {
905     for (var i = 0; i < this.uuids.length; i++)
906       this.uuids[i] = device.uuids[i];
907   }
908 };
909
910 function BluetoothSocket(uuid, peer, msg) {
911   _addConstProperty(this, 'uuid', uuid);
912   _addConstProperty(this, 'peer', peer);
913   _addConstProperty(this, 'state', BluetoothSocketState.OPEN);
914   this.onclose = null;
915   this.onmessage = null;
916   this.data = [];
917   this.channel = 0;
918   this.socket_fd = 0;
919
920   if (msg) {
921     this.channel = msg.channel;
922     this.socket_fd = msg.socket_fd;
923   }
924 }
925
926 var BluetoothSocketState = {
927   'CLOSE': 1,
928   'OPEN': 2
929 };
930 Object.defineProperty(BluetoothSocket, 'BluetoothSocketState', {
931   configurable: false,
932   writable: false,
933   value: BluetoothSocketState
934 });
935
936
937 BluetoothSocket.prototype.writeData = function(data) {
938   // make sure that socket is connected and opened.
939   if (this.state == BluetoothSocketState.CLOSE) {
940     throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
941     return;
942   }
943
944   var msg = {
945     'cmd': 'SocketWriteData',
946     'data': data,
947     'socket_fd': this.socket_fd
948   };
949   var result = JSON.parse(extension.internal.sendSyncMessage(JSON.stringify(msg)));
950
951   return result.size;
952 };
953
954 BluetoothSocket.prototype.readData = function() {
955   return this.data;
956 };
957
958 BluetoothSocket.prototype.close = function() {
959   var msg = {
960     'cmd': 'CloseSocket',
961     'socket_fd': this.socket_fd
962   };
963
964   postMessage(msg, function(result) {
965     if (result.error) {
966       console.log('Can\'t close socket (' + this.socket_fd + ').');
967       throw new tizen.WebAPIException(tizen.WebAPIException.UNKNOWN_ERR);
968       return;
969     }
970
971     // FIXME(clecou) Update socket object state only when using Tizen C API backend.
972     // BlueZ4 backend independently updates socket state based on a dbus callback mechanism.
973     // A better approach would be to adapt backends instances to have a single JSON protocol.
974     if (result.capi) {
975       for (var i in adapter.sockets) {
976         var socket = adapter.sockets[i];
977         if (socket.socket_fd === msg.socket_fd) {
978           if (socket.onclose && typeof socket.onmessage === 'function') {
979             _addConstProperty(adapter.sockets[i], 'state', BluetoothSocketState.CLOSE);
980             socket.onclose();
981           }
982         }
983       }
984     }
985   });
986 };
987
988 function BluetoothClass() {}
989 BluetoothClass.prototype.hasService = function(service) {
990   for (var i = 0; i < this.services.length; i++)
991     if (this.services[i] === service)
992       return true;
993
994     return false;
995 };
996
997 function BluetoothServiceHandler(name, uuid, msg) {
998   _addConstProperty(this, 'name', name);
999   _addConstProperty(this, 'uuid', uuid);
1000   _addConstProperty(this, 'isConnected', false);
1001
1002   if (msg) {
1003     this.server_fd = msg.server_fd;
1004     this.sdp_handle = msg.sdp_handle;
1005     this.channel = msg.channel;
1006   }
1007 }
1008
1009 BluetoothServiceHandler.prototype.unregister = function(successCallback, errorCallback) {
1010   if (!xwalk.utils.validateArguments('?ff', arguments)) {
1011     throw new tizen.WebAPIError(tizen.WebAPIException.TYPE_MISMATCH_ERR);
1012   }
1013
1014   if (adapter.checkServiceAvailability(errorCallback))
1015     return;
1016
1017   var msg = {
1018     'cmd': 'UnregisterServer',
1019     'server_fd': this.server_fd,
1020     'sdp_handle': this.sdp_handle
1021   };
1022
1023   postMessage(msg, function(result) {
1024     if (result.error != 0) {
1025       if (errorCallback) {
1026         var error = new tizen.WebAPIError(tizen.WebAPIException.UNKNOWN_ERR);
1027         errorCallback(error);
1028       }
1029
1030       throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
1031       return;
1032     }
1033
1034     _addConstProperty(this, 'isConnected', false);
1035     if (successCallback)
1036       successCallback();
1037   });
1038 };