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