[Bluetooth][bugfix] Call success callbacks in registerService()
[platform/core/api/webapi-plugins.git] / src / bluetooth / bluetooth_api.js
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 var T = xwalk.utils.type;
18 var Converter = xwalk.utils.converter;
19 var AV = xwalk.utils.validator;
20 var Privilege = xwalk.utils.privilege;
21 var privUtils_ = xwalk.utils;
22 var native = new xwalk.utils.NativeManager(extension);
23
24 var AbortError = new WebAPIException('AbortError', 'An unknown error occurred');
25
26 // class BluetoothClassDeviceMajor /////////////////////////////////////////
27 var BluetoothClassDeviceMajor = function() {
28     Object.defineProperties(this, {
29         MISC: { value: 0x00, writable: false, enumerable: true },
30         COMPUTER: { value: 0x01, writable: false, enumerable: true },
31         PHONE: { value: 0x02, writable: false, enumerable: true },
32         NETWORK: { value: 0x03, writable: false, enumerable: true },
33         AUDIO_VIDEO: { value: 0x04, writable: false, enumerable: true },
34         PERIPHERAL: { value: 0x05, writable: false, enumerable: true },
35         IMAGING: { value: 0x06, writable: false, enumerable: true },
36         WEARABLE: { value: 0x07, writable: false, enumerable: true },
37         TOY: { value: 0x08, writable: false, enumerable: true },
38         HEALTH: { value: 0x09, writable: false, enumerable: true },
39         UNCATEGORIZED: { value: 0x1f, writable: false, enumerable: true }
40     });
41 };
42
43 // class BluetoothClassDeviceMinor /////////////////////////////////////////
44 var BluetoothClassDeviceMinor = function() {
45     Object.defineProperties(this, {
46         COMPUTER_UNCATEGORIZED: { value: 0x00, writable: false, enumerable: true },
47         COMPUTER_DESKTOP: { value: 0x01, writable: false, enumerable: true },
48         COMPUTER_SERVER: { value: 0x02, writable: false, enumerable: true },
49         COMPUTER_LAPTOP: { value: 0x03, writable: false, enumerable: true },
50         COMPUTER_HANDHELD_PC_OR_PDA: { value: 0x04, writable: false, enumerable: true },
51         COMPUTER_PALM_PC_OR_PDA: { value: 0x05, writable: false, enumerable: true },
52         COMPUTER_WEARABLE: { value: 0x06, writable: false, enumerable: true },
53
54         PHONE_UNCATEGORIZED: { value: 0x00, writable: false, enumerable: true },
55         PHONE_CELLULAR: { value: 0x01, writable: false, enumerable: true },
56         PHONE_CORDLESS: { value: 0x02, writable: false, enumerable: true },
57         PHONE_SMARTPHONE: { value: 0x03, writable: false, enumerable: true },
58         PHONE_MODEM_OR_GATEWAY: { value: 0x04, writable: false, enumerable: true },
59         PHONE_ISDN: { value: 0x05, writable: false, enumerable: true },
60
61         AV_UNRECOGNIZED: { value: 0x00, writable: false, enumerable: true },
62         AV_WEARABLE_HEADSET: { value: 0x01, writable: false, enumerable: true },
63         AV_HANDSFREE: { value: 0x02, writable: false, enumerable: true },
64         AV_MICROPHONE: { value: 0x04, writable: false, enumerable: true },
65         AV_LOUDSPEAKER: { value: 0x05, writable: false, enumerable: true },
66         AV_HEADPHONES: { value: 0x06, writable: false, enumerable: true },
67         AV_PORTABLE_AUDIO: { value: 0x07, writable: false, enumerable: true },
68         AV_CAR_AUDIO: { value: 0x08, writable: false, enumerable: true },
69         AV_SETTOP_BOX: { value: 0x09, writable: false, enumerable: true },
70         AV_HIFI: { value: 0x0a, writable: false, enumerable: true },
71         AV_VCR: { value: 0x0b, writable: false, enumerable: true },
72         AV_VIDEO_CAMERA: { value: 0x0c, writable: false, enumerable: true },
73         AV_CAMCORDER: { value: 0x0d, writable: false, enumerable: true },
74         AV_MONITOR: { value: 0x0e, writable: false, enumerable: true },
75         AV_DISPLAY_AND_LOUDSPEAKER: { value: 0x0f, writable: false, enumerable: true },
76         AV_VIDEO_CONFERENCING: { value: 0x10, writable: false, enumerable: true },
77         AV_GAMING_TOY: { value: 0x12, writable: false, enumerable: true },
78
79         PERIPHERAL_UNCATEGORIZED: { value: 0x00, writable: false, enumerable: true },
80         PERIPHERAL_KEYBOARD: { value: 0x10, writable: false, enumerable: true },
81         PERIPHERAL_POINTING_DEVICE: { value: 0x20, writable: false, enumerable: true },
82         PERIPHERAL_KEYBOARD_AND_POINTING_DEVICE: {
83             value: 0x30,
84             writable: false,
85             enumerable: true
86         },
87         PERIPHERAL_JOYSTICK: { value: 0x01, writable: false, enumerable: true },
88         PERIPHERAL_GAMEPAD: { value: 0x02, writable: false, enumerable: true },
89         PERIPHERAL_REMOTE_CONTROL: { value: 0x03, writable: false, enumerable: true },
90         PERIPHERAL_SENSING_DEVICE: { value: 0x04, writable: false, enumerable: true },
91         PERIPHERAL_DEGITIZER_TABLET: { value: 0x05, writable: false, enumerable: true },
92         PERIPHERAL_CARD_READER: { value: 0x06, writable: false, enumerable: true },
93         PERIPHERAL_DIGITAL_PEN: { value: 0x07, writable: false, enumerable: true },
94         PERIPHERAL_HANDHELD_SCANNER: { value: 0x08, writable: false, enumerable: true },
95         PERIPHERAL_HANDHELD_INPUT_DEVICE: {
96             value: 0x09,
97             writable: false,
98             enumerable: true
99         },
100
101         IMAGING_UNCATEGORIZED: { value: 0x00, writable: false, enumerable: true },
102         IMAGING_DISPLAY: { value: 0x04, writable: false, enumerable: true },
103         IMAGING_CAMERA: { value: 0x08, writable: false, enumerable: true },
104         IMAGING_SCANNER: { value: 0x10, writable: false, enumerable: true },
105         IMAGING_PRINTER: { value: 0x20, writable: false, enumerable: true },
106
107         WEARABLE_WRITST_WATCH: { value: 0x01, writable: false, enumerable: true },
108         WEARABLE_PAGER: { value: 0x02, writable: false, enumerable: true },
109         WEARABLE_JACKET: { value: 0x03, writable: false, enumerable: true },
110         WEARABLE_HELMET: { value: 0x04, writable: false, enumerable: true },
111         WEARABLE_GLASSES: { value: 0x05, writable: false, enumerable: true },
112
113         TOY_ROBOT: { value: 0x01, writable: false, enumerable: true },
114         TOY_VEHICLE: { value: 0x02, writable: false, enumerable: true },
115         TOY_DOLL: { value: 0x03, writable: false, enumerable: true },
116         TOY_CONTROLLER: { value: 0x04, writable: false, enumerable: true },
117         TOY_GAME: { value: 0x05, writable: false, enumerable: true },
118
119         HEALTH_UNDEFINED: { value: 0x00, writable: false, enumerable: true },
120         HEALTH_BLOOD_PRESSURE_MONITOR: { value: 0x01, writable: false, enumerable: true },
121         HEALTH_THERMOMETER: { value: 0x02, writable: false, enumerable: true },
122         HEALTH_WEIGHING_SCALE: { value: 0x03, writable: false, enumerable: true },
123         HEALTH_GLUCOSE_METER: { value: 0x04, writable: false, enumerable: true },
124         HEALTH_PULSE_OXIMETER: { value: 0x05, writable: false, enumerable: true },
125         HEALTH_PULSE_RATE_MONITOR: { value: 0x06, writable: false, enumerable: true },
126         HEALTH_DATA_DISPLAY: { value: 0x07, writable: false, enumerable: true },
127         HEALTH_STEP_COUNTER: { value: 0x08, writable: false, enumerable: true },
128         HEALTH_BODY_COMPOSITION_ANALYZER: {
129             value: 0x09,
130             writable: false,
131             enumerable: true
132         },
133         HEALTH_PEAK_FLOW_MONITOR: { value: 0x0a, writable: false, enumerable: true },
134         HEALTH_MEDICATION_MONITOR: { value: 0x0b, writable: false, enumerable: true },
135         HEALTH_KNEE_PROSTHESIS: { value: 0x0c, writable: false, enumerable: true },
136         HEALTH_ANKLE_PROSTHESIS: { value: 0x0d, writable: false, enumerable: true }
137     });
138 };
139
140 // class BluetoothClassDeviceService ///////////////////////////////////////
141 var BluetoothClassDeviceService = function() {
142     Object.defineProperties(this, {
143         LIMITED_DISCOVERABILITY: { value: 0x0001, writable: false, enumerable: true },
144         POSITIONING: { value: 0x0008, writable: false, enumerable: true },
145         NETWORKING: { value: 0x0010, writable: false, enumerable: true },
146         RENDERING: { value: 0x0020, writable: false, enumerable: true },
147         CAPTURING: { value: 0x0040, writable: false, enumerable: true },
148         OBJECT_TRANSFER: { value: 0x0080, writable: false, enumerable: true },
149         AUDIO: { value: 0x0100, writable: false, enumerable: true },
150         TELEPHONY: { value: 0x0200, writable: false, enumerable: true },
151         INFORMATION: { value: 0x0400, writable: false, enumerable: true }
152     });
153 };
154
155 //class tizen.BluetoothLEServiceData ///////////////////////////
156 tizen.BluetoothLEServiceData = function(d) {
157     AV.isConstructorCall(this, tizen.BluetoothLEServiceData);
158     var uuid_ = '';
159     var data_ = '';
160
161     Object.defineProperties(this, {
162         uuid: {
163             enumerable: true,
164             get: function() {
165                 return uuid_;
166             },
167             set: function(v) {
168                 uuid_ = Converter.toString(v);
169             }
170         },
171         data: {
172             enumerable: true,
173             get: function() {
174                 return data_;
175             },
176             set: function(v) {
177                 try {
178                     data_ = BluetoothManager_toDOMString(v);
179                 } catch (err) {
180                     data_ = Converter.toString(v);
181                 }
182             }
183         }
184     });
185
186     if (arguments.length >= 2) {
187         // public constructor
188         this.uuid = arguments[0];
189         this.data = arguments[1];
190     } else if (d && T.isObject(d)) {
191         // internal constructor
192         this.uuid = d.uuid;
193         this.data = d.data;
194     } else {
195         uuid_ = undefined;
196         data_ = undefined;
197     }
198 };
199
200 //class BluetoothLEAdvertiseData ///////////////////////////
201 tizen.BluetoothLEAdvertiseData = function(dict) {
202     AV.isConstructorCall(this, tizen.BluetoothLEAdvertiseData);
203     var includeName_ = false;
204     var uuids_ = null;
205     var solicitationuuids_ = null;
206     var appearance_ = null;
207     var includeTxPowerLevel_ = false;
208     var serviceData_ = null;
209     var servicesData_ = null;
210     var manufacturerData_ = null;
211
212     Object.defineProperties(this, {
213         includeName: {
214             enumerable: true,
215             get: function() {
216                 return includeName_;
217             },
218             set: function(v) {
219                 includeName_ = Converter.toBoolean(v, true);
220             }
221         },
222         uuids: {
223             enumerable: true,
224             get: function() {
225                 return uuids_;
226             },
227             set: function(v) {
228                 if (T.isNull(v)) {
229                     uuids_ = v;
230                 } else if (T.isArray(v)) {
231                     for (var i = 0; i < v.length; ++i) {
232                         if (!T.isString(v[i])) {
233                             v[i] = Converter.toString(v[i]);
234                         }
235                     }
236                     uuids_ = v;
237                 }
238             }
239         },
240         solicitationuuids: {
241             enumerable: true,
242             get: function() {
243                 return solicitationuuids_;
244             },
245             set: function(v) {
246                 if (T.isNull(v)) {
247                     solicitationuuids_ = v;
248                 } else if (T.isArray(v)) {
249                     for (var i = 0; i < v.length; ++i) {
250                         if (!T.isString(v[i])) {
251                             v[i] = Converter.toString(v[i]);
252                         }
253                     }
254                     solicitationuuids_ = v;
255                 }
256             }
257         },
258         appearance: {
259             enumerable: true,
260             get: function() {
261                 return appearance_;
262             },
263             set: function(v) {
264                 appearance_ = Converter.toUnsignedLong(v, true);
265             }
266         },
267         includeTxPowerLevel: {
268             enumerable: true,
269             get: function() {
270                 return includeTxPowerLevel_;
271             },
272             set: function(v) {
273                 includeTxPowerLevel_ = Converter.toBoolean(v, true);
274             }
275         },
276         serviceData: {
277             enumerable: true,
278             get: function() {
279                 return serviceData_;
280             },
281             set: function(v) {
282                 if (T.isNull(v) || v instanceof tizen.BluetoothLEServiceData) {
283                     serviceData_ = v;
284                 }
285             }
286         },
287         servicesData: {
288             enumerable: true,
289             get: function() {
290                 return servicesData_;
291             },
292             set: function(v) {
293                 if (T.isNull(v)) {
294                     servicesData_ = v;
295                 } else if (T.isArray(v)) {
296                     var tmpArray = [];
297                     for (var i = 0; i < v.length; ++i) {
298                         if (v[i] instanceof tizen.BluetoothLEServiceData) {
299                             tmpArray.push(v[i]);
300                         }
301                     }
302                     servicesData_ = tmpArray;
303                 }
304             }
305         },
306         manufacturerData: {
307             enumerable: true,
308             get: function() {
309                 return manufacturerData_;
310             },
311             set: function(v) {
312                 if (T.isNull(v) || v instanceof tizen.BluetoothLEManufacturerData) {
313                     manufacturerData_ = v;
314                 }
315             }
316         }
317     });
318
319     if (T.isObject(dict)) {
320         var o = {};
321
322         // includeName
323         if (T.isNull(dict.includeName) || T.isBoolean(dict.includeName)) {
324             o.includeName = dict.includeName;
325         } else if (!T.isUndefined(dict.includeName)) {
326             return;
327         }
328
329         // uuids
330         if (T.isNull(dict.uuids)) {
331             o.uuids = dict.uuids;
332         } else if (T.isArray(dict.uuids)) {
333             for (var i = 0; i < dict.uuids.length; ++i) {
334                 if (!T.isString(dict.uuids[i])) {
335                     return;
336                 }
337             }
338             o.uuids = dict.uuids;
339         } else if (!T.isUndefined(dict.uuids)) {
340             return;
341         }
342
343         // solicitationuuids
344         if (T.isNull(dict.solicitationuuids)) {
345             o.solicitationuuids = dict.solicitationuuids;
346         } else if (T.isArray(dict.solicitationuuids)) {
347             for (var i = 0; i < dict.solicitationuuids.length; ++i) {
348                 if (!T.isString(dict.solicitationuuids[i])) {
349                     return;
350                 }
351             }
352             o.solicitationuuids = dict.solicitationuuids;
353         } else if (!T.isUndefined(dict.solicitationuuids)) {
354             return;
355         }
356
357         // appearance
358         if (T.isNull(dict.appearance) || T.isNumber(dict.appearance)) {
359             o.appearance = dict.appearance;
360         } else if (!T.isUndefined(dict.appearance)) {
361             return;
362         }
363
364         // includeTxPowerLevel
365         if (T.isNull(dict.includeTxPowerLevel) || T.isBoolean(dict.includeTxPowerLevel)) {
366             o.includeTxPowerLevel = dict.includeTxPowerLevel;
367         } else if (!T.isUndefined(dict.includeTxPowerLevel)) {
368             return;
369         }
370
371         // serviceData
372         if (
373             T.isNull(dict.serviceData) ||
374             dict.serviceData instanceof tizen.BluetoothLEServiceData
375         ) {
376             o.serviceData = dict.serviceData;
377         } else if (!T.isUndefined(dict.serviceData)) {
378             return;
379         }
380
381         // servicesData
382         if (T.isNull(dict.servicesData) || T.isArray(dict.servicesData)) {
383             // additional validation is done in setter of BluetoothLEAdvertiseData.servicesData
384             o.servicesData = dict.servicesData;
385         }
386
387         // manufacturerData
388         if (
389             T.isNull(dict.manufacturerData) ||
390             dict.manufacturerData instanceof tizen.BluetoothLEManufacturerData
391         ) {
392             o.manufacturerData = dict.manufacturerData;
393         } else if (!T.isUndefined(dict.manufacturerData)) {
394             return;
395         }
396
397         for (var prop in o) {
398             if (o.hasOwnProperty(prop) && this.hasOwnProperty(prop)) {
399                 this[prop] = o[prop];
400             }
401         }
402     }
403 };
404
405 //class tizen.BluetoothLEManufacturerData ///////////////////////////
406 tizen.BluetoothLEManufacturerData = function(d) {
407     AV.isConstructorCall(this, tizen.BluetoothLEManufacturerData);
408     var id_ = '';
409     var data_ = '';
410
411     Object.defineProperties(this, {
412         id: {
413             enumerable: true,
414             get: function() {
415                 return id_;
416             },
417             set: function(v) {
418                 id_ = Converter.toString(v);
419             }
420         },
421         data: {
422             enumerable: true,
423             get: function() {
424                 return data_;
425             },
426             set: function(v) {
427                 try {
428                     data_ = BluetoothManager_toDOMString(v);
429                 } catch (err) {
430                     data_ = Converter.toString(v);
431                 }
432             }
433         }
434     });
435
436     if (arguments.length >= 2) {
437         // public constructor
438         this.id = arguments[0];
439         this.data = arguments[1];
440     } else if (d && T.isObject(d)) {
441         // internal constructor
442         this.id = d.id;
443         this.data = d.data;
444     } else {
445         id_ = undefined;
446         data_ = undefined;
447     }
448 };
449
450 // class BluetoothClass ///////////////////////////
451 var BluetoothClass = function(data) {
452     var services = [];
453     if (data) {
454         services = data.services;
455     }
456
457     Object.defineProperties(this, {
458         major: { value: data.major, writable: false, enumerable: true },
459         minor: { value: data.minor, writable: false, enumerable: true },
460         services: {
461             enumerable: true,
462             set: function() {},
463             get: function() {
464                 return services.slice();
465             }
466         }
467     });
468 };
469
470 var BluetoothClass_hasService = function() {
471     privUtils_.log('Entered BluetoothClass.hasService()');
472     privUtils_.checkPrivilegeAccess4Ver(
473         '2.4',
474         Privilege.BLUETOOTH,
475         Privilege.BLUETOOTH_GAP
476     );
477
478     var args = AV.validateArgs(arguments, [
479         {
480             name: 'service',
481             type: AV.Types.UNSIGNED_LONG
482         }
483     ]);
484
485     var size = this.services.length;
486     for (var i = 0; i < size; i++) {
487         if (this.services[i] === args.service) {
488             return true;
489         }
490     }
491     return false;
492 };
493
494 BluetoothClass.prototype.hasService = function() {
495     return BluetoothClass_hasService.apply(this, arguments);
496 };
497
498 // class BluetoothSocket ///////////////////////////
499 var _BLUETOOTH_SOCKET_STATE_CLOSED = 'CLOSED';
500
501 function BluetoothSocketListeners() {
502     var that = this;
503     this.socketCallback = function(data) {
504         var event = data;
505         var socket = that.sockets[event.id];
506
507         if (socket) {
508             if ('onclose' === event.event) {
509                 // no more events
510                 that.removeListener(event.id);
511                 // change state
512                 Object.defineProperty(socket, 'state', {
513                     value: _BLUETOOTH_SOCKET_STATE_CLOSED
514                 });
515             }
516
517             var callback = socket[event.event];
518             if (T.isFunction(callback)) {
519                 callback();
520             }
521         } else {
522             privUtils_.log('Received event for an unknown socket: ' + event.id);
523         }
524     };
525 }
526
527 BluetoothSocketListeners.prototype.sockets = {};
528
529 BluetoothSocketListeners.prototype.addListener = function(socket) {
530     if (T.isEmptyObject(this.sockets)) {
531         native.addListener('BLUETOOTH_SOCKET_STATE_CHANGED', this.socketCallback);
532     }
533
534     this.sockets[socket._id] = socket;
535 };
536
537 BluetoothSocketListeners.prototype.removeListener = function(id) {
538     delete this.sockets[id];
539
540     if (T.isEmptyObject(this.sockets)) {
541         native.removeListener('BLUETOOTH_SOCKET_STATE_CHANGED', this.socketCallback);
542     }
543 };
544
545 var _bluetoothSocketListeners = new BluetoothSocketListeners();
546
547 var BluetoothSocket = function(data) {
548     Object.defineProperties(this, {
549         uuid: { value: data.uuid, writable: false, enumerable: true },
550         state: {
551             value: data.state,
552             writable: false,
553             enumerable: true,
554             configurable: true
555         },
556         peer: {
557             value: new BluetoothDevice(data.peer),
558             writable: false,
559             enumerable: true
560         },
561         onmessage: { value: null, writable: true, enumerable: true },
562         onclose: { value: null, writable: true, enumerable: true },
563         _id: { value: data.id, writable: false, enumerable: false }
564     });
565
566     _bluetoothSocketListeners.addListener(this);
567 };
568
569 BluetoothSocket.prototype.writeData = function(data) {
570     privUtils_.log('Entered BluetoothSocket.writeData()');
571
572     var byteData = BluetoothManager_toByteArray(data);
573
574     var callArgs = {
575         id: this._id,
576         data: byteData
577     };
578
579     var result = native.callSync('BluetoothSocketWriteData', callArgs);
580
581     if (native.isFailure(result)) {
582         throw native.getErrorObject(result);
583     } else {
584         return native.getResultObject(result);
585     }
586 };
587
588 BluetoothSocket.prototype.readData = function() {
589     privUtils_.log('Entered BluetoothSocket.readData()');
590
591     var callArgs = {
592         id: this._id
593     };
594
595     var result = native.callSync('BluetoothSocketReadData', callArgs);
596
597     if (native.isFailure(result)) {
598         throw native.getErrorObject(result);
599     } else {
600         return native.getResultObject(result);
601     }
602 };
603
604 BluetoothSocket.prototype.close = function() {
605     privUtils_.log('Entered BluetoothSocket.close()');
606
607     if (_BLUETOOTH_SOCKET_STATE_CLOSED !== this.state) {
608         var callArgs = {
609             id: this._id
610         };
611
612         var result = native.callSync('BluetoothSocketClose', callArgs);
613
614         if (native.isFailure(result)) {
615             throw native.getErrorObject(result);
616         }
617
618         // change state
619         Object.defineProperty(this, 'state', { value: _BLUETOOTH_SOCKET_STATE_CLOSED });
620     }
621 };
622
623 //class BluetoothLEDevice ///////////////////////////
624 var BluetoothLEDevice = function(data) {
625     var address = '',
626         name = null,
627         txpowerlevel = null,
628         appearance = null,
629         uuids = null,
630         solicitationuuids = null,
631         serviceData = null,
632         manufacturerData = null,
633         rssi = null;
634
635     if (data) {
636         address = data.address;
637         name = data.name || null;
638         txpowerlevel = data.txpowerlevel || null;
639         appearance = data.appearance || null;
640         uuids = data.uuids || null;
641         solicitationuuids = data.solicitationuuids || null;
642         if (data.serviceData) {
643             serviceData = [];
644             data.serviceData.forEach(function(d) {
645                 serviceData.push(new tizen.BluetoothLEServiceData(d));
646             });
647         }
648         if (data.manufacturerData) {
649             manufacturerData = new tizen.BluetoothLEManufacturerData(
650                 data.manufacturerData
651             );
652         }
653         if (data.rssi) {
654             rssi = data.rssi;
655         }
656     }
657
658     Object.defineProperties(this, {
659         address: { value: address, writable: false, enumerable: true },
660         name: { value: name, writable: false, enumerable: true },
661         txpowerlevel: { value: txpowerlevel, writable: false, enumerable: true },
662         appearance: { value: appearance, writable: false, enumerable: true },
663         uuids: {
664             enumerable: true,
665             set: function() {},
666             get: function() {
667                 var service_uuids = uuids ? uuids.slice() : null;
668                 return service_uuids;
669             }
670         },
671         solicitationuuids: {
672             enumerable: true,
673             set: function() {},
674             get: function() {
675                 return solicitationuuids ? solicitationuuids.slice() : null;
676             }
677         },
678         serviceData: {
679             enumerable: true,
680             set: function() {},
681             get: function() {
682                 return serviceData ? serviceData.slice() : null;
683             }
684         },
685         manufacturerData: {
686             value: manufacturerData,
687             writable: false,
688             enumerable: true
689         },
690         rssi: { value: rssi, writable: false, enumerable: true }
691     });
692 };
693
694 BluetoothLEDevice.prototype.connect = function() {
695     privUtils_.log('Entered BluetoothLEDevice.connect()');
696     var args = AV.validateArgs(arguments, [
697         {
698             name: 'successCallback',
699             type: AV.Types.FUNCTION,
700             optional: true,
701             nullable: true
702         },
703         {
704             name: 'errorCallback',
705             type: AV.Types.FUNCTION,
706             optional: true,
707             nullable: true
708         }
709     ]);
710
711     var callback = function(result) {
712         if (native.isFailure(result)) {
713             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
714         } else {
715             native.callIfPossible(args.successCallback);
716         }
717     };
718     // Errors are handled by error callback
719     var result = native.call(
720         'BluetoothLEDeviceConnect',
721         { address: this.address },
722         callback
723     );
724     if (native.isFailure(result)) {
725         throw native.getErrorObject(result);
726     }
727 };
728
729 BluetoothLEDevice.prototype.disconnect = function() {
730     privUtils_.log('Entered BluetoothLEDevice.disconnect()');
731     var args = AV.validateArgs(arguments, [
732         {
733             name: 'successCallback',
734             type: AV.Types.FUNCTION,
735             optional: true,
736             nullable: true
737         },
738         {
739             name: 'errorCallback',
740             type: AV.Types.FUNCTION,
741             optional: true,
742             nullable: true
743         }
744     ]);
745     var callback = function(result) {
746         if (native.isFailure(result)) {
747             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
748         } else {
749             native.callIfPossible(args.successCallback);
750         }
751     };
752
753     var result = native.call(
754         'BluetoothLEDeviceDisconnect',
755         { address: this.address },
756         callback
757     );
758     if (native.isFailure(result)) {
759         throw native.getErrorObject(result);
760     }
761 };
762
763 BluetoothLEDevice.prototype.getService = function() {
764     privUtils_.log('Entered BluetoothLEDevice.getService()');
765     var args = AV.validateArgs(arguments, [
766         {
767             name: 'uuid',
768             type: AV.Types.STRING
769         }
770     ]);
771
772     var callArgs = {
773         uuid: args.uuid,
774         address: this.address
775     };
776
777     var result = native.callSync('BluetoothLEDeviceGetService', callArgs);
778     if (native.isFailure(result)) {
779         throw native.getErrorObject(result);
780     } else {
781         return new BluetoothGATTService(native.getResultObject(result));
782     }
783 };
784
785 BluetoothLEDevice.prototype.getServiceAllUuids = function() {
786     privUtils_.log('Entered BluetoothLEDevice.getServiceAllUuids()');
787
788     var callArgs = {
789         address: this.address
790     };
791
792     var result = native.callSync('BluetoothLEDeviceGetServiceAllUuids', callArgs);
793     if (native.isFailure(result)) {
794         throw native.getErrorObject(result);
795     } else {
796         var uuids = native.getResultObject(result);
797         return uuids;
798     }
799 };
800
801 BluetoothLEDevice.prototype.isConnected = function() {
802     privUtils_.log('Entered BluetoothLEDevice.isConnected()');
803
804     var callArgs = {
805         address: this.address
806     };
807
808     var result = native.callSync('BluetoothLEDeviceIsConnected', callArgs);
809     if (native.isFailure(result)) {
810         throw native.getErrorObject(result);
811     }
812     return native.getResultObject(result);
813 };
814
815 BluetoothLEDevice.prototype.addConnectStateChangeListener = function() {
816     privUtils_.log('Entered BluetoothLEDevice.addConnectStateChangeListener()');
817     var args = AV.validateArgs(arguments, [
818         {
819             name: 'listener',
820             type: AV.Types.LISTENER,
821             values: ['onconnected', 'ondisconnected']
822         }
823     ]);
824
825     var that = this;
826
827     var func = function(event) {
828         if (event.address === that.address && args.listener[event.action]) {
829             args.listener[event.action](that);
830         }
831     };
832
833     var watchId = _bleConnectChangeListener.addListener(func);
834
835     return watchId;
836 };
837
838 BluetoothLEDevice.prototype.removeConnectStateChangeListener = function() {
839     privUtils_.log('Entered BluetoothLEDevice.removeConnectStateChangeListener()');
840
841     var args = AV.validateArgs(arguments, [
842         {
843             name: 'watchID',
844             type: AV.Types.LONG
845         }
846     ]);
847
848     _bleConnectChangeListener.removeListener(args.watchID);
849 };
850
851 BluetoothLEDevice.prototype.getAttMtu = function() {
852     privUtils_.log('Entered BluetoothLEDevice.getAttMtu()');
853
854     var callArgs = {
855         address: this.address
856     };
857
858     var result = native.callSync('BluetoothLEDeviceGetAttMtu', callArgs);
859     if (native.isFailure(result)) {
860         throw native.getErrorObject(result);
861     }
862     return native.getResultObject(result);
863 };
864
865 BluetoothLEDevice.prototype.requestAttMtuChange = function() {
866     privUtils_.log('Entered BluetoothLEDevice.requestAttMtuChange()');
867
868     var args = AV.validateArgs(arguments, [
869         {
870             name: 'mtu',
871             type: AV.Types.LONG
872         }
873     ]);
874
875     var callArgs = {
876         address: this.address,
877         mtu: args.mtu
878     };
879
880     var result = native.callSync('BluetoothLEDeviceRequestAttMtuChange', callArgs);
881     if (native.isFailure(result)) {
882         throw native.getErrorObject(result);
883     }
884     return native.getResultObject(result);
885 };
886
887 BluetoothLEDevice.prototype.addAttMtuChangeListener = function() {
888     privUtils_.log('Entered BluetoothLEDevice.addAttMtuChangeListener()');
889     var args = AV.validateArgs(arguments, [
890         {
891             name: 'callback',
892             type: AV.Types.FUNCTION
893         }
894     ]);
895
896     var callArgs = { address: this.address };
897
898     var callback = function(result) {
899         privUtils_.log('Entered BluetoothLEDevice.addAttMtuChangeListener() callback');
900         args.callback(result.attMtuValue);
901     };
902
903     var watchId = _bleAttMtuChangeListener.addListener(callback, callArgs);
904
905     return watchId;
906 };
907
908 BluetoothLEDevice.prototype.removeAttMtuChangeListener = function() {
909     privUtils_.log('Entered BluetoothLEDevice.removeAttMtuChangeListener()');
910
911     var args = AV.validateArgs(arguments, [
912         {
913             name: 'watchID',
914             type: AV.Types.LONG
915         }
916     ]);
917
918     var callArgs = { address: this.address };
919
920     _bleAttMtuChangeListener.removeListener(args.watchID, callArgs);
921 };
922
923 // class BluetoothDevice ///////////////////////////
924 var BluetoothDevice = function(data) {
925     var self = this;
926     function _getter(field) {
927         var callArgs = {};
928
929         callArgs.address = self.address;
930         callArgs.field = field;
931
932         var result = native.callSync('BluetoothDeviceGetBoolValue', callArgs);
933
934         if (native.isFailure(result)) {
935             return false;
936         } else {
937             return native.getResultObject(result);
938         }
939     }
940
941     function isBondedGetter() {
942         return _getter('isBonded');
943     }
944
945     function isTrustedGetter() {
946         return _getter('isTrusted');
947     }
948
949     function isConnectedGetter() {
950         return _getter('isConnected');
951     }
952
953     var uuids = [];
954     if (data) {
955         uuids = data.uuids;
956     }
957
958     Object.defineProperties(this, {
959         name: { value: data.name, writable: false, enumerable: true },
960         address: { value: data.address, writable: false, enumerable: true },
961         deviceClass: {
962             value: new BluetoothClass(data.deviceClass),
963             writable: false,
964             enumerable: true
965         },
966         isBonded: {
967             enumerable: true,
968             set: function() {},
969             get: isBondedGetter
970         },
971         isTrusted: {
972             enumerable: true,
973             set: function() {},
974             get: isTrustedGetter
975         },
976         isConnected: {
977             enumerable: true,
978             set: function() {},
979             get: isConnectedGetter
980         },
981         uuids: {
982             enumerable: true,
983             set: function() {},
984             get: function() {
985                 return uuids.slice();
986             }
987         }
988     });
989 };
990
991 BluetoothDevice.prototype.connectToServiceByUUID = function() {
992     privUtils_.log('Entered BluetoothDevice.connectToServiceByUUID()');
993
994     var args = AV.validateArgs(arguments, [
995         {
996             name: 'uuid',
997             type: AV.Types.STRING
998         },
999         {
1000             name: 'successCallback',
1001             type: AV.Types.FUNCTION
1002         },
1003         {
1004             name: 'errorCallback',
1005             type: AV.Types.FUNCTION,
1006             optional: true,
1007             nullable: true
1008         }
1009     ]);
1010
1011     var callArgs = {
1012         address: this.address,
1013         uuid: args.uuid
1014     };
1015     var callback = function(result) {
1016         if (native.isFailure(result)) {
1017             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1018         } else {
1019             args.successCallback(new BluetoothSocket(native.getResultObject(result)));
1020         }
1021     };
1022
1023     var result = native.call('BluetoothDeviceConnectToServiceByUUID', callArgs, callback);
1024     if (native.isFailure(result)) {
1025         throw native.getErrorObject(result);
1026     }
1027 };
1028
1029 // class BluetoothServiceHandler ///////////////////////////
1030 function BluetoothServiceListeners() {
1031     var that = this;
1032     this.serviceCallback = function(data) {
1033         var e = data;
1034         var service = that.services[e.uuid];
1035         var result = new BluetoothSocket(e);
1036
1037         if (service) {
1038             privUtils_.log(service);
1039             service.onconnect(result);
1040         }
1041     };
1042 }
1043
1044 BluetoothServiceListeners.prototype.services = {};
1045
1046 BluetoothServiceListeners.prototype.addListener = function(service) {
1047     if (T.isEmptyObject(this.services)) {
1048         native.addListener('BLUETOOTH_SERVICE_ONCONNECT', this.serviceCallback);
1049     }
1050
1051     this.services[service.uuid] = service;
1052 };
1053
1054 BluetoothServiceListeners.prototype.removeListener = function(uuid) {
1055     delete this.services[uuid];
1056
1057     if (T.isEmptyObject(this.services)) {
1058         native.removeListener('BLUETOOTH_SERVICE_ONCONNECT', this.serviceCallback);
1059     }
1060 };
1061
1062 var _bluetoothServiceListeners = new BluetoothServiceListeners();
1063
1064 var BluetoothServiceHandler = function(data) {
1065     function isConnectedGetter() {
1066         var callArgs = {
1067             uuid: this.uuid
1068         };
1069
1070         var result = native.callSync('BluetoothAdapterIsServiceConnected', {
1071             uuid: this.uuid
1072         });
1073
1074         if (native.isFailure(result)) {
1075             return false;
1076         } else {
1077             return native.getResultObject(result);
1078         }
1079     }
1080
1081     Object.defineProperties(this, {
1082         uuid: { value: data.uuid, writable: false, enumerable: true },
1083         name: { value: data.name, writable: false, enumerable: true },
1084         isConnected: {
1085             enumerable: true,
1086             set: function() {},
1087             get: isConnectedGetter
1088         },
1089         onconnect: { value: null, writable: true, enumerable: true }
1090     });
1091
1092     _bluetoothServiceListeners.addListener(this);
1093 };
1094
1095 BluetoothServiceHandler.prototype.unregister = function() {
1096     privUtils_.log('Entered BluetoothServiceHandler.unregister()');
1097     var args = AV.validateArgs(arguments, [
1098         {
1099             name: 'successCallback',
1100             type: AV.Types.FUNCTION,
1101             optional: true,
1102             nullable: true
1103         },
1104         {
1105             name: 'errorCallback',
1106             type: AV.Types.FUNCTION,
1107             optional: true,
1108             nullable: true
1109         }
1110     ]);
1111
1112     var callArgs = {
1113         uuid: this.uuid
1114     };
1115
1116     var callback = function(result) {
1117         if (native.isFailure(result)) {
1118             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1119         } else {
1120             native.callIfPossible(args.successCallback);
1121         }
1122     };
1123
1124     var result = native.call('BluetoothServiceHandlerUnregister', callArgs, callback);
1125     if (native.isFailure(result)) {
1126         throw native.getErrorObject(result);
1127     }
1128
1129     _bluetoothServiceListeners.removeListener(this.uuid);
1130 };
1131
1132 // class BluetoothHealthApplication ///////////////////////////
1133 function BluetoothHealthApplicationListeners() {
1134     var that = this;
1135     this.appCallback = function(data) {
1136         var event = data;
1137         var app = that.apps[event.id];
1138
1139         if (app) {
1140             var callback = app[event.event];
1141             if (T.isFunction(callback)) {
1142                 var param;
1143                 switch (event.event) {
1144                 case 'onconnect':
1145                     param = new BluetoothHealthChannel(native.getResultObject(event));
1146                     break;
1147
1148                 default:
1149                     privUtils_.log('Unknown event: ' + event.event);
1150                     break;
1151                 }
1152                 callback(param);
1153             }
1154         } else {
1155             privUtils_.log('Received event for an unknown application: ' + event.id);
1156         }
1157     };
1158 }
1159
1160 BluetoothHealthApplicationListeners.prototype.apps = {};
1161
1162 BluetoothHealthApplicationListeners.prototype.addListener = function(app) {
1163     if (T.isEmptyObject(this.apps)) {
1164         native.addListener('BLUETOOTH_HEALTH_APPLICATION_CHANGED', this.appCallback);
1165     }
1166
1167     this.apps[app._id] = app;
1168 };
1169
1170 BluetoothHealthApplicationListeners.prototype.removeListener = function(id) {
1171     delete this.apps[id];
1172
1173     if (T.isEmptyObject(this.apps)) {
1174         native.removeListener('BLUETOOTH_HEALTH_APPLICATION_CHANGED', this.appCallback);
1175     }
1176 };
1177
1178 var _bluetoothHealthApplicationListeners = new BluetoothHealthApplicationListeners();
1179
1180 var BluetoothHealthApplication = function(data) {
1181     Object.defineProperties(this, {
1182         dataType: { value: data.dataType, writable: false, enumerable: true },
1183         name: { value: data.name, writable: false, enumerable: true },
1184         onconnect: { value: null, writable: true, enumerable: true },
1185         _id: { value: data._id, writable: false, enumerable: false }
1186     });
1187
1188     _bluetoothHealthApplicationListeners.addListener(this);
1189 };
1190
1191 BluetoothHealthApplication.prototype.unregister = function() {
1192     privUtils_.log('Entered BluetoothHealthApplication.unregister()');
1193     privUtils_.printDeprecationWarningFor('BluetoothHealthApplication');
1194     var args = AV.validateArgs(arguments, [
1195         {
1196             name: 'successCallback',
1197             type: AV.Types.FUNCTION,
1198             optional: true,
1199             nullable: true
1200         },
1201         {
1202             name: 'errorCallback',
1203             type: AV.Types.FUNCTION,
1204             optional: true,
1205             nullable: true
1206         }
1207     ]);
1208
1209     var callArgs = { id: this._id };
1210
1211     var callback = function(result) {
1212         if (native.isFailure(result)) {
1213             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1214         } else {
1215             native.callIfPossible(args.successCallback);
1216         }
1217     };
1218
1219     var result = native.call('BluetoothHealthApplicationUnregister', callArgs, callback);
1220     if (native.isFailure(result)) {
1221         throw native.getErrorObject(result);
1222     }
1223
1224     _bluetoothHealthApplicationListeners.removeListener(this._id);
1225 };
1226
1227 // class BluetoothProfileHandler ///////////////////////////
1228 var _BluetoothProfileType = {
1229     HEALTH: 'HEALTH'
1230 };
1231
1232 var BluetoothProfileHandler = function(data) {
1233     if (data) {
1234         var profileType = data.profileType;
1235         function profileTypeGetter() {
1236             privUtils_.printDeprecationWarningFor('profileType');
1237             return profileType;
1238         }
1239         Object.defineProperties(this, {
1240             profileType: { enumerable: true, set: function() {}, get: profileTypeGetter }
1241         });
1242     }
1243 };
1244
1245 // class BluetoothHealthProfileHandler ///////////////////////////
1246 var BluetoothHealthProfileHandler = function(data) {
1247     BluetoothProfileHandler.call(this, data);
1248 };
1249
1250 BluetoothHealthProfileHandler.prototype = new BluetoothProfileHandler();
1251
1252 BluetoothHealthProfileHandler.prototype.constructor = BluetoothProfileHandler;
1253
1254 BluetoothHealthProfileHandler.prototype.registerSinkApplication = function() {
1255     privUtils_.log('Entered BluetoothHealthProfileHandler.registerSinkApplication()');
1256     privUtils_.printDeprecationWarningFor('BluetoothHealthProfileHandler');
1257
1258     var args = AV.validateArgs(arguments, [
1259         {
1260             name: 'dataType',
1261             type: AV.Types.LONG // there's no short type
1262         },
1263         {
1264             name: 'name',
1265             type: AV.Types.STRING
1266         },
1267         {
1268             name: 'successCallback',
1269             type: AV.Types.FUNCTION
1270         },
1271         {
1272             name: 'errorCallback',
1273             type: AV.Types.FUNCTION,
1274             optional: true,
1275             nullable: true
1276         }
1277     ]);
1278
1279     var callArgs = {
1280         dataType: args.dataType,
1281         name: args.name
1282     };
1283
1284     var callback = function(result) {
1285         if (native.isFailure(result)) {
1286             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1287         } else {
1288             args.successCallback(
1289                 new BluetoothHealthApplication(native.getResultObject(result))
1290             );
1291         }
1292     };
1293
1294     var result = native.call(
1295         'BluetoothHealthProfileHandlerRegisterSinkApp',
1296         callArgs,
1297         callback
1298     );
1299     if (native.isFailure(result)) {
1300         throw native.getErrorObject(result);
1301     }
1302 };
1303
1304 BluetoothHealthProfileHandler.prototype.connectToSource = function() {
1305     privUtils_.log('Entered BluetoothHealthProfileHandler.connectToSource()');
1306     privUtils_.printDeprecationWarningFor('BluetoothHealthProfileHandler');
1307
1308     var args = AV.validateArgs(arguments, [
1309         {
1310             name: 'peer',
1311             type: AV.Types.PLATFORM_OBJECT,
1312             values: BluetoothDevice
1313         },
1314         {
1315             name: 'application',
1316             type: AV.Types.PLATFORM_OBJECT,
1317             values: BluetoothHealthApplication
1318         },
1319         {
1320             name: 'successCallback',
1321             type: AV.Types.FUNCTION
1322         },
1323         {
1324             name: 'errorCallback',
1325             type: AV.Types.FUNCTION,
1326             optional: true,
1327             nullable: true
1328         }
1329     ]);
1330
1331     var callArgs = {
1332         address: args.peer.address,
1333         appId: args.application._id
1334     };
1335
1336     var callback = function(result) {
1337         if (native.isFailure(result)) {
1338             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1339         } else {
1340             var channel = native.getResultObject(result);
1341             channel.peer = args.peer;
1342             channel.appId = args.application._id;
1343             args.successCallback(new BluetoothHealthChannel(channel));
1344         }
1345     };
1346
1347     var result = native.call(
1348         'BluetoothHealthProfileHandlerConnectToSource',
1349         callArgs,
1350         callback
1351     );
1352     if (native.isFailure(result)) {
1353         throw native.getErrorObject(result);
1354     }
1355 };
1356
1357 // class BluetoothHealthChannel ///////////////////////////
1358 var BluetoothHealthChannel = function(data) {
1359     Object.defineProperties(this, {
1360         peer: { value: data.peer, writable: false, enumerable: true },
1361         channelType: { value: data.channelType, writable: false, enumerable: true },
1362         application: {
1363             value: _bluetoothHealthApplicationListeners.apps[data.appId],
1364             writable: false,
1365             enumerable: true
1366         },
1367         isConnected: {
1368             value: data.isConnected,
1369             writable: false,
1370             enumerable: true,
1371             configurable: true
1372         },
1373         _id: { value: data._id, writable: false, enumerable: false }
1374     });
1375 };
1376
1377 BluetoothHealthChannel.prototype.close = function() {
1378     privUtils_.log('Entered BluetoothHealthChannel.close()');
1379     privUtils_.printDeprecationWarningFor('BluetoothHealthChannel');
1380
1381     if (this.isConnected) {
1382         var callArgs = {
1383             channel: this._id,
1384             address: this.peer.address
1385         };
1386
1387         var result = native.callSync('BluetoothHealthChannelClose', callArgs);
1388
1389         if (native.isFailure(result)) {
1390             throw native.getErrorObject(result);
1391         }
1392
1393         Object.defineProperty(this, 'isConnected', { value: false });
1394     }
1395 };
1396
1397 BluetoothHealthChannel.prototype.sendData = function() {
1398     privUtils_.log('Entered BluetoothHealthChannel.sendData()');
1399     privUtils_.printDeprecationWarningFor('BluetoothHealthChannel');
1400
1401     var args = AV.validateArgs(arguments, [
1402         {
1403             name: 'data',
1404             type: AV.Types.ARRAY,
1405             values: AV.Types.BYTE
1406         }
1407     ]);
1408
1409     var callArgs = {
1410         channel: this._id,
1411         data: args.data
1412     };
1413
1414     var result = native.callSync('BluetoothHealthChannelSendData', callArgs);
1415
1416     if (native.isFailure(result)) {
1417         throw native.getErrorObject(result);
1418     } else {
1419         return native.getResultObject(result);
1420     }
1421 };
1422
1423 var _healthListeners = {};
1424
1425 function _BluetoothHealthChannelChangeCallback(event) {
1426     var e = event;
1427     var callback = _healthListeners[e.id];
1428     var d;
1429
1430     switch (e.event) {
1431     case 'onmessage':
1432         d = e.data;
1433         break;
1434
1435     case 'onclose':
1436         break;
1437
1438     default:
1439         privUtils_.log('Unknown mode: ' + e.event);
1440         return;
1441     }
1442
1443     if (callback[e.event]) {
1444         callback[e.event](d);
1445     }
1446 }
1447
1448 var BluetoothHealthChannel_setListener = function() {
1449     privUtils_.log('Entered BluetoothHealthChannel.setListener()');
1450     privUtils_.printDeprecationWarningFor('BluetoothHealthChannel');
1451     privUtils_.checkPrivilegeAccess4Ver(
1452         '2.4',
1453         Privilege.BLUETOOTH,
1454         Privilege.BLUETOOTH_HEALTH
1455     );
1456     var args = AV.validateArgs(arguments, [
1457         {
1458             name: 'changeCallback',
1459             type: AV.Types.LISTENER,
1460             values: ['onmessage', 'onclose']
1461         }
1462     ]);
1463
1464     if (T.isEmptyObject(_healthListeners)) {
1465         native.addListener(
1466             'BluetoothHealthChannelChangeCallback',
1467             _BluetoothHealthChannelChangeCallback
1468         );
1469     }
1470     _healthListeners[this._id] = args.changeCallback;
1471 };
1472
1473 BluetoothHealthChannel.prototype.setListener = function() {
1474     BluetoothHealthChannel_setListener.apply(this, arguments);
1475 };
1476
1477 var BluetoothHealthChannel_unsetListener = function() {
1478     privUtils_.log('Entered BluetoothHealthChannel.unsetListener ()');
1479     privUtils_.printDeprecationWarningFor('BluetoothHealthChannel');
1480     if (T.isEmptyObject(_healthListeners)) {
1481         privUtils_.checkPrivilegeAccess4Ver(
1482             '2.4',
1483             Privilege.BLUETOOTH,
1484             Privilege.BLUETOOTH_HEALTH
1485         );
1486     }
1487
1488     delete _healthListeners[this._id];
1489
1490     if (T.isEmptyObject(_healthListeners)) {
1491         native.removeListener(
1492             'BluetoothHealthChannelChangeCallback',
1493             _BluetoothHealthChannelChangeCallback
1494         );
1495     }
1496 };
1497
1498 BluetoothHealthChannel.prototype.unsetListener = function() {
1499     privUtils_.printDeprecationWarningFor('BluetoothHealthChannel');
1500     BluetoothHealthChannel_unsetListener.apply(this, arguments);
1501 };
1502
1503 /**
1504  * Creates a manager for specified listener event.
1505  *
1506  * @param {string} name - name of the listener this manager handles
1507  * @param {function} callback - function to be invoked when event specified by the name
1508  *                              fires.
1509  *                              This function should return false if the callback
1510  *                              doesn't want to handle the event anymore, true otherwise.
1511  *                              This function should have following signature:
1512  *                              bool callback(event, successCallback, errorCallback);
1513  *
1514  * @return {object} object which allows to add or remove callbacks for specified listener
1515  */
1516 function _singleListenerBuilder(name, callback) {
1517     var listenerName = name;
1518     var successCallback;
1519     var errorCallback;
1520     var callbackFunction = callback;
1521     var listenerRegistered = false;
1522
1523     function innerCallback(event) {
1524         if (!callbackFunction(event, successCallback, errorCallback)) {
1525             removeListener();
1526         }
1527     }
1528
1529     function addListener(s, e) {
1530         successCallback = s;
1531         errorCallback = e;
1532
1533         if (!listenerRegistered) {
1534             native.addListener(listenerName, innerCallback);
1535             listenerRegistered = true;
1536         }
1537     }
1538
1539     function removeListener() {
1540         if (listenerRegistered) {
1541             native.removeListener(listenerName, innerCallback);
1542             listenerRegistered = false;
1543         }
1544
1545         successCallback = undefined;
1546         errorCallback = undefined;
1547     }
1548
1549     return {
1550         addListener: addListener,
1551         removeListener: removeListener
1552     };
1553 }
1554
1555 var _bleScanListener = _singleListenerBuilder('BluetoothLEScanCallback', function(
1556     event,
1557     successCallback,
1558     errorCallback
1559 ) {
1560     var d;
1561     var ret = true;
1562
1563     switch (event.action) {
1564     case 'onsuccess':
1565         d = new BluetoothLEDevice(event.data);
1566         break;
1567
1568     case 'onerror':
1569         if (errorCallback) {
1570             errorCallback(native.getErrorObject(event));
1571         }
1572         return ret;
1573
1574     default:
1575         privUtils_.log('Unknown mode: ' + event.action);
1576         return ret;
1577     }
1578     if (successCallback) {
1579         successCallback(d);
1580     }
1581
1582     return ret;
1583 });
1584
1585 var _bleAdvertiseListener = _singleListenerBuilder(
1586     'BluetoothLEAdvertiseCallback',
1587     function(event, successCallback, errorCallback) {
1588         var d;
1589         var ret = true;
1590
1591         switch (event.action) {
1592         case 'onstate':
1593             if (successCallback) {
1594                 successCallback(native.getResultObject(event));
1595                 if (native.getResultObject(event) == 'STOPPED') {
1596                     _bleAdvertiseListener.removeListener();
1597                 }
1598             }
1599             return ret;
1600
1601         case 'onerror':
1602             if (errorCallback) {
1603                 errorCallback(native.getErrorObject(event));
1604             }
1605             return ret;
1606
1607         default:
1608             privUtils_.log('Unknown mode: ' + event.action);
1609             return ret;
1610         }
1611     }
1612 );
1613
1614 //class BluetoothLEAdapter ///////////////////////////
1615 var BluetoothLEAdapter = function() {};
1616
1617 BluetoothLEAdapter.prototype.startScan = function() {
1618     privUtils_.log('Entered BluetoothLEAdapter.startScan()');
1619     var args = AV.validateArgs(arguments, [
1620         {
1621             name: 'successCallback',
1622             type: AV.Types.FUNCTION
1623         },
1624         {
1625             name: 'errorCallback',
1626             type: AV.Types.FUNCTION,
1627             optional: true,
1628             nullable: true
1629         }
1630     ]);
1631
1632     var result = native.callSync('BluetoothLEAdapterStartScan', {});
1633     if (native.isFailure(result)) {
1634         throw native.getErrorObject(result);
1635     }
1636
1637     _bleScanListener.addListener(args.successCallback, args.errorCallback);
1638 };
1639
1640 BluetoothLEAdapter.prototype.stopScan = function() {
1641     privUtils_.log('Entered BluetoothLEAdapter.stopScan()');
1642
1643     _bleScanListener.removeListener();
1644
1645     var result = native.callSync('BluetoothLEAdapterStopScan', {});
1646     if (native.isFailure(result)) {
1647         throw native.getErrorObject(result);
1648     }
1649 };
1650
1651 BluetoothLEAdapter.prototype.isScanning = function() {
1652     privUtils_.log('Entered BluetoothLEAdapter.isScanning()');
1653
1654     var result = native.callSync('BluetoothLEAdapterIsScanning', {});
1655     if (native.isFailure(result)) {
1656         throw native.getErrorObject(result);
1657     }
1658     return native.getResultObject(result);
1659 };
1660
1661 var _BluetoothAdvertisePacketType = {
1662     ADVERTISE: 'ADVERTISE',
1663     SCAN_RESPONSE: 'SCAN_RESPONSE'
1664 };
1665
1666 var _BluetoothAdvertisingMode = {
1667     BALANCED: 'BALANCED',
1668     LOW_LATENCY: 'LOW_LATENCY',
1669     LOW_ENERGY: 'LOW_ENERGY'
1670 };
1671
1672 BluetoothLEAdapter.prototype.startAdvertise = function() {
1673     privUtils_.log('Entered BluetoothLEAdapter.startAdvertise()');
1674     var args = AV.validateArgs(arguments, [
1675         {
1676             name: 'advertiseData',
1677             type: AV.Types.PLATFORM_OBJECT,
1678             values: tizen.BluetoothLEAdvertiseData
1679         },
1680         {
1681             name: 'packetType',
1682             type: AV.Types.ENUM,
1683             values: T.getValues(_BluetoothAdvertisePacketType)
1684         },
1685         {
1686             name: 'successCallback',
1687             type: AV.Types.FUNCTION
1688         },
1689         {
1690             name: 'errorCallback',
1691             type: AV.Types.FUNCTION,
1692             optional: true,
1693             nullable: true
1694         },
1695         {
1696             name: 'mode',
1697             type: AV.Types.ENUM,
1698             values: T.getValues(_BluetoothAdvertisingMode),
1699             optional: true,
1700             nullable: true
1701         },
1702         {
1703             name: 'connectable',
1704             type: AV.Types.BOOLEAN,
1705             optional: true,
1706             nullable: true
1707         }
1708     ]);
1709
1710     var callArgs = {
1711         advertiseData: args.advertiseData,
1712         packetType: args.packetType,
1713         mode: T.isNullOrUndefined(args.mode)
1714             ? _BluetoothAdvertisingMode.BALANCED
1715             : args.mode,
1716         connectable: T.isNullOrUndefined(args.connectable) ? true : args.connectable
1717     };
1718
1719     var result = native.callSync('BluetoothLEAdapterStartAdvertise', callArgs);
1720
1721     if (native.isFailure(result)) {
1722         throw native.getErrorObject(result);
1723     }
1724
1725     _bleAdvertiseListener.addListener(args.successCallback, args.errorCallback);
1726 };
1727
1728 BluetoothLEAdapter.prototype.stopAdvertise = function() {
1729     privUtils_.log('Entered BluetoothLEAdapter.stopAdvertise()');
1730
1731     var result = native.callSync('BluetoothLEAdapterStopAdvertise', {});
1732
1733     if (native.isFailure(result)) {
1734         throw native.getErrorObject(result);
1735     }
1736 };
1737
1738 BluetoothLEAdapter.prototype.addConnectStateChangeListener = function() {
1739     privUtils_.log('Entered BluetoothLEAdapter.addClientConnectStateChaneListener()');
1740     var args = AV.validateArgs(arguments, [
1741         {
1742             name: 'listener',
1743             type: AV.Types.LISTENER,
1744             values: ['onconnected', 'ondisconnected']
1745         }
1746     ]);
1747
1748     var func = function(event) {
1749         if (args.listener[event.action]) {
1750             args.listener[event.action](new BluetoothLEDevice(event.address));
1751         }
1752     };
1753
1754     return _bleConnectChangeListener.addListener(func);
1755 };
1756
1757 BluetoothLEAdapter.prototype.removeConnectStateChangeListener = function() {
1758     privUtils_.log('Entered BluetoothLEAdapter.removeConnectStateChangeListener()');
1759
1760     var args = AV.validateArgs(arguments, [
1761         {
1762             name: 'watchID',
1763             type: AV.Types.LONG
1764         }
1765     ]);
1766
1767     _bleConnectChangeListener.removeListener(args.watchID);
1768 };
1769
1770 //class BluetoothGATTService ///////////////////////////
1771 var BluetoothGATTService = function(data, address) {
1772     var handle_ = data.handle;
1773     var uuid_ = data.uuid;
1774     var serviceUuid_ = data.serviceUuid;
1775     //address_ is needed to control if device is still connected
1776     var address_ = address || data.address;
1777
1778     function servicesGetter() {
1779         var services = [];
1780         var result = native.callSync('BluetoothGATTClientServiceGetServices', {
1781             handle: handle_,
1782             address: address_
1783         });
1784         if (native.isSuccess(result)) {
1785             var resultObject = native.getResultObject(result);
1786             resultObject.forEach(function(s) {
1787                 services.push(new BluetoothGATTService(s, address_));
1788             });
1789         }
1790         return services;
1791     }
1792     function characteristicsGetter() {
1793         var characteristics = [];
1794         var result = native.callSync('BluetoothGATTClientServiceGetCharacteristics', {
1795             handle: handle_,
1796             uuid: serviceUuid_,
1797             address: address_
1798         });
1799         if (native.isSuccess(result)) {
1800             var resultObject = native.getResultObject(result);
1801             resultObject.forEach(function(c) {
1802                 if (!T.isNullOrUndefined(c)) {
1803                     characteristics.push(new BluetoothGATTCharacteristic(c, address_));
1804                 }
1805             });
1806         }
1807         return characteristics;
1808     }
1809
1810     /*
1811      * If this function is called as a constructor of BluetoothGATTServerService's
1812      * base class, some properties have to be left configurable, to enable redefinition.
1813      * Otherwise, if this function is called to create BluetoothGATTService,
1814      * they are left non-configurable.
1815      *
1816      * If BluetoothGATTService will be a base for any other class in the future,
1817      * the line below may have to be updated to take into account the new class.
1818      */
1819     var isConfigurable = this instanceof BluetoothGATTServerService;
1820
1821     Object.defineProperties(this, {
1822         uuid: { value: uuid_, writable: false, enumerable: true },
1823         serviceUuid: { value: serviceUuid_, writable: false, enumerable: true },
1824         services: {
1825             enumerable: true,
1826             configurable: isConfigurable,
1827             set: function() {},
1828             get: servicesGetter
1829         },
1830         characteristics: {
1831             enumerable: true,
1832             configurable: isConfigurable,
1833             set: function() {},
1834             get: characteristicsGetter
1835         }
1836     });
1837 };
1838
1839 var CurrentGATTServerEntityId = 0;
1840 function NextGattServerEntityID() {
1841     return ++CurrentGATTServerEntityId;
1842 }
1843
1844 function IsUuidValid(uuid) {
1845     return (
1846         BluetoothManager_UUIDIsValid16Bit(uuid) ||
1847         BluetoothManager_UUIDIsValid32Bit(uuid) ||
1848         BluetoothManager_UUIDIsValid128Bit(uuid)
1849     );
1850 }
1851
1852 var ValidateBluetoothGATTServerServiceInit = function(initData) {
1853     initData = AV.validateArgs(
1854         [
1855             initData['serviceUuid'],
1856             initData['isPrimary'] || true,
1857             initData['includedServices'] || [],
1858             initData['characteristics'] || []
1859         ],
1860         [
1861             {
1862                 name: 'serviceUuid',
1863                 type: AV.Types.STRING,
1864                 validator: IsUuidValid
1865             },
1866             {
1867                 name: 'isPrimary',
1868                 type: AV.Types.BOOLEAN
1869             },
1870             {
1871                 name: 'includedServices',
1872                 type: AV.Types.ARRAY
1873             },
1874             {
1875                 name: 'characteristics',
1876                 type: AV.Types.ARRAY
1877             }
1878         ]
1879     );
1880
1881     // "uuid" field is used to construct BluetoothGATTService, but it's not a part
1882     // of BluetoothGATTServerServiceInit dictionary.
1883     // In case of BluetoothGATTServerServices, its value is always the same as
1884     // serviceUuid's.
1885     initData.uuid = initData.serviceUuid;
1886     return initData;
1887 };
1888
1889 //class BluetoothGATTServerService ///////////////////////////
1890 var BluetoothGATTServerService = function(data) {
1891     data = ValidateBluetoothGATTServerServiceInit(data);
1892
1893     BluetoothGATTService.call(this, data, null);
1894
1895     var services_ = data.includedServices.map(function(serviceData) {
1896         return new BluetoothGATTServerService(serviceData, null);
1897     });
1898     var characteristics_ = data.characteristics.map(function(characteristicData) {
1899         return new BluetoothGATTServerCharacteristic(characteristicData, null);
1900     });
1901
1902     Object.defineProperties(this, {
1903         isPrimary: { value: data.isPrimary, writable: false, enumerable: true },
1904         services: {
1905             enumerable: true,
1906             get: function() {
1907                 return services_.slice();
1908             },
1909             set: function() {}
1910         },
1911         characteristics: {
1912             enumerable: true,
1913             get: function() {
1914                 return characteristics_.slice();
1915             },
1916             set: function() {}
1917         },
1918         // This property is "private" and meant not to be used by users
1919         _id: {
1920             // It has to be enumerable, to be serialized with JSON.stringify()
1921             enumerable: true,
1922             value: NextGattServerEntityID()
1923         }
1924     });
1925 };
1926
1927 BluetoothGATTServerService.prototype = Object.create(BluetoothGATTService.prototype);
1928
1929 Object.defineProperty(BluetoothGATTServerService.prototype, 'constructor', {
1930     value: BluetoothGATTServerService,
1931     enumerable: false,
1932     writable: true
1933 });
1934
1935 function _getIncludedServicesAndItsComponentsIdsRecursively(service) {
1936     var ids = [];
1937
1938     for (var serviceIndex = 0; serviceIndex < service.services.length; ++serviceIndex) {
1939         ids.push(service.services[serviceIndex]._id);
1940         ids = ids.concat(
1941             _getIncludedServicesAndItsComponentsIdsRecursively(
1942                 service.services[serviceIndex]
1943             )
1944         );
1945     }
1946
1947     for (
1948         var characteristicIndex = 0;
1949         characteristicIndex < service.characteristics.length;
1950         ++characteristicIndex
1951     ) {
1952         ids.push(service.characteristics[characteristicIndex]._id);
1953
1954         for (
1955             var descriptorIndex = 0;
1956             descriptorIndex <
1957             service.characteristics[characteristicIndex].descriptors.length;
1958             ++descriptorIndex
1959         ) {
1960             ids.push(
1961                 service.characteristics[characteristicIndex].descriptors[descriptorIndex]
1962                     ._id
1963             );
1964         }
1965     }
1966
1967     return ids;
1968 }
1969
1970 var BluetoothGATTServer_valid_unregisterService_errors = [
1971     'InvalidStateError',
1972     'AbortError'
1973 ];
1974 var BluetoothGATTServer_valid_unregisterService_exceptions = [
1975     'TypeMismatchError',
1976     'SecurityError'
1977 ];
1978
1979 BluetoothGATTServerService.prototype.unregister = function() {
1980     var args = AV.validateArgs(arguments, [
1981         {
1982             name: 'successCallback',
1983             type: AV.Types.FUNCTION,
1984             optional: true,
1985             nullable: true
1986         },
1987         {
1988             name: 'errorCallback',
1989             type: AV.Types.FUNCTION,
1990             optional: true,
1991             nullable: true
1992         }
1993     ]);
1994
1995     var serviceIndex = _BluetoothGATTServerServices.findIndex(
1996         function(service) {
1997             return service._id === this._id;
1998         }.bind(this)
1999     );
2000
2001     if (serviceIndex === -1) {
2002         throw new WebAPIException(
2003             'AbortError',
2004             'The service is not registered in the local GATT server or is included in another service'
2005         );
2006     }
2007
2008     function removeFromJSArrayAndCallSuccessCb() {
2009         _BluetoothGATTServerServices.splice(serviceIndex, 1);
2010         native.callIfPossible(args.successCallback);
2011     }
2012
2013     if (!_BluetoothGATTServerServicesRegisteredInNativeLayer[this._id]) {
2014         removeFromJSArrayAndCallSuccessCb();
2015         return;
2016     }
2017
2018     var callback = function(result) {
2019         if (native.isFailure(result)) {
2020             native.callIfPossible(
2021                 args.errorCallback,
2022                 native.getErrorObjectAndValidate(
2023                     result,
2024                     BluetoothGATTServer_valid_unregisterService_errors,
2025                     AbortError
2026                 )
2027             );
2028         } else {
2029             delete _BluetoothGATTServerServicesRegisteredInNativeLayer[this._id];
2030             removeFromJSArrayAndCallSuccessCb();
2031         }
2032     }.bind(this);
2033
2034     var callArgs = {
2035         _id: this._id,
2036         idsToRemoveFromNativeLayer: _getIncludedServicesAndItsComponentsIdsRecursively(
2037             this
2038         )
2039     };
2040     var result = native.call('BluetoothGATTServerUnregisterService', callArgs, callback);
2041
2042     if (native.isFailure(result)) {
2043         throw native.getErrorObjectAndValidate(
2044             result,
2045             BluetoothGATTServer_valid_unregisterService_errors,
2046             AbortError
2047         );
2048     }
2049 };
2050
2051 function _setReadWriteValueRequestCallbacksIfPresent(
2052     object,
2053     readValueRequestCallback,
2054     readValueSendResponseSuccessCallback,
2055     readValueSendResponseErrorCallback,
2056     writeValueRequestCallback,
2057     writeValueSendResponseSuccessCallback,
2058     writeValueSendResponseErrorCallback,
2059     successCallback,
2060     errorCallback
2061 ) {
2062     if (
2063         object instanceof BluetoothGATTServerCharacteristic ||
2064         object instanceof BluetoothGATTServerDescriptor
2065     ) {
2066         if (readValueRequestCallback) {
2067             object.setReadValueRequestCallback(
2068                 readValueRequestCallback,
2069                 successCallback,
2070                 errorCallback,
2071                 readValueSendResponseSuccessCallback,
2072                 readValueSendResponseErrorCallback
2073             );
2074         }
2075         if (writeValueRequestCallback) {
2076             object.setWriteValueRequestCallback(
2077                 writeValueRequestCallback,
2078                 successCallback,
2079                 errorCallback,
2080                 writeValueSendResponseSuccessCallback,
2081                 writeValueSendResponseErrorCallback
2082             );
2083         }
2084     }
2085 }
2086
2087 function _countCallbackToRegisterInService(service) {
2088     var count = 0;
2089     for (var serviceIndex = 0; serviceIndex < service.services.length; ++serviceIndex) {
2090         count = count + _countCallbackToRegisterInService(service.services[serviceIndex]);
2091     }
2092
2093     for (
2094         var characteristicIndex = 0;
2095         characteristicIndex < service.characteristics.length;
2096         ++characteristicIndex
2097     ) {
2098         var characteristic = service.characteristics[characteristicIndex];
2099         if (characteristic['_readValueRequestCallback']) {
2100             count++;
2101         }
2102         if (characteristic['_writeValueRequestCallback']) {
2103             count++;
2104         }
2105         for (
2106             var descriptorIndex = 0;
2107             descriptorIndex < characteristic.descriptors.length;
2108             ++descriptorIndex
2109         ) {
2110             var descriptor = characteristic.descriptors[descriptorIndex];
2111             if (descriptor['_readValueRequestCallback']) {
2112                 count++;
2113             }
2114             if (descriptor['_writeValueRequestCallback']) {
2115                 count++;
2116             }
2117         }
2118     }
2119     return count;
2120 }
2121
2122 function _registerReadWriteValueRequestCallbacksInGATTServiceRecursively(
2123     service,
2124     callbacksAggregator
2125 ) {
2126     for (var serviceIndex = 0; serviceIndex < service.services.length; ++serviceIndex) {
2127         _registerReadWriteValueRequestCallbacksInGATTServiceRecursively(
2128             service.services[serviceIndex],
2129             callbacksAggregator
2130         );
2131     }
2132
2133     for (
2134         var characteristicIndex = 0;
2135         characteristicIndex < service.characteristics.length;
2136         ++characteristicIndex
2137     ) {
2138         var characteristic = service.characteristics[characteristicIndex];
2139         // Callbacks for characteristics
2140         _setReadWriteValueRequestCallbacksIfPresent(
2141             characteristic,
2142             characteristic['_readValueRequestCallback'],
2143             characteristic['_readValueSendResponseSuccessCallback'],
2144             characteristic['_readValueSendResponseErrorCallback'],
2145             characteristic['_writeValueRequestCallback'],
2146             characteristic['_writeValueSendResponseSuccessCallback'],
2147             characteristic['_writeValueSendResponseErrorCallback'],
2148             callbacksAggregator.successCallback,
2149             callbacksAggregator.errorCallback
2150         );
2151         for (
2152             var descriptorIndex = 0;
2153             descriptorIndex < characteristic.descriptors.length;
2154             ++descriptorIndex
2155         ) {
2156             // Callbacks for descriptors
2157             var descriptor = characteristic.descriptors[descriptorIndex];
2158             _setReadWriteValueRequestCallbacksIfPresent(
2159                 descriptor,
2160                 descriptor['_readValueRequestCallback'],
2161                 descriptor['_readValueSendResponseSuccessCallback'],
2162                 descriptor['_readValueSendResponseErrorCallback'],
2163                 descriptor['_writeValueRequestCallback'],
2164                 descriptor['_writeValueSendResponseSuccessCallback'],
2165                 descriptor['_writeValueSendResponseErrorCallback'],
2166                 callbacksAggregator.successCallback,
2167                 callbacksAggregator.errorCallback
2168             );
2169         }
2170     }
2171 }
2172
2173 function _registerReadWriteValueRequestCallbacksInGATTService(
2174     service,
2175     successCallback,
2176     errorCallback
2177 ) {
2178     var count = _countCallbackToRegisterInService(service);
2179
2180     if (count === 0) {
2181       successCallback();
2182       return;
2183     }
2184
2185     var callbacksAggregator = new ResultCallbacksAggregator(
2186         count,
2187         function onAllSucceeded() {
2188             successCallback();
2189         },
2190         function onFailure(error) {
2191             errorCallback(error);
2192         }
2193     );
2194
2195     _registerReadWriteValueRequestCallbacksInGATTServiceRecursively(
2196         service,
2197         callbacksAggregator
2198     );
2199 }
2200
2201 var numberArrayToByteArray = function(array) {
2202     var d = [];
2203
2204     array.forEach(function(b) {
2205         d.push(Converter.toOctet(b));
2206     });
2207     return d;
2208 };
2209
2210 //class BluetoothGATTCharacteristic ///////////////////////////
2211 var BluetoothGATTCharacteristic = function(data, address) {
2212     if (!T.isObject(data)) {
2213         return null;
2214     }
2215     var address_ = address;
2216     var handle_ = data.handle;
2217     var descriptors_ = data.descriptors.map(function(descriptor_data) {
2218         return new BluetoothGATTDescriptor(descriptor_data, address_);
2219     });
2220     var isBroadcast_ = data.isBroadcast;
2221     var hasExtendedProperties_ = data.hasExtendedProperties;
2222     var isNotify_ = data.isNotify;
2223     var isIndication_ = data.isIndication;
2224     var isReadable_ = data.isReadable;
2225     var isSignedWrite_ = data.isSignedWrite;
2226     var isWritable_ = data.isWritable;
2227     var isWriteNoResponse_ = data.isWriteNoResponse;
2228     var uuid_ = data.uuid;
2229
2230     /*
2231      * If this function is called as a constructor of BluetoothGATTServerCharacteristic's
2232      * base class, some properties have to be left configurable, to enable redefinition.
2233      * Otherwise, if this function is called to create BluetoothGATTCharacteristic,
2234      * they are left non-configurable.
2235      *
2236      * If BluetoothGATTCharacteristic will be a base for any other class in the future,
2237      * the line below may have to be updated to take into account the new class.
2238      */
2239     var isConfigurable = this instanceof BluetoothGATTServerCharacteristic;
2240
2241     Object.defineProperties(this, {
2242         descriptors: {
2243             enumerable: true,
2244             configurable: isConfigurable,
2245             get: function() {
2246                 return descriptors_.slice();
2247             },
2248             set: function() {}
2249         },
2250         isBroadcast: {
2251             enumerable: true,
2252             get: function() {
2253                 return isBroadcast_;
2254             },
2255             set: function() {}
2256         },
2257         hasExtendedProperties: {
2258             enumerable: true,
2259             get: function() {
2260                 return hasExtendedProperties_;
2261             },
2262             set: function() {}
2263         },
2264         isNotify: {
2265             enumerable: true,
2266             get: function() {
2267                 return isNotify_;
2268             },
2269             set: function() {}
2270         },
2271         isIndication: {
2272             enumerable: true,
2273             get: function() {
2274                 return isIndication_;
2275             },
2276             set: function() {}
2277         },
2278         isReadable: {
2279             enumerable: true,
2280             get: function() {
2281                 return isReadable_;
2282             },
2283             set: function() {}
2284         },
2285         isSignedWrite: {
2286             enumerable: true,
2287             get: function() {
2288                 return isSignedWrite_;
2289             },
2290             set: function() {}
2291         },
2292         isWritable: {
2293             enumerable: true,
2294             get: function() {
2295                 return isWritable_;
2296             },
2297             set: function() {}
2298         },
2299         isWriteNoResponse: {
2300             enumerable: true,
2301             get: function() {
2302                 return isWriteNoResponse_;
2303             },
2304             set: function() {}
2305         },
2306         uuid: {
2307             enumerable: true,
2308             get: function() {
2309                 return uuid_;
2310             },
2311             set: function() {}
2312         }
2313     });
2314
2315     this.readValue = function() {
2316         privUtils_.log('Entered BluetoothGATTCharacteristic.readValue()');
2317         var args = AV.validateArgs(arguments, [
2318             {
2319                 name: 'successCallback',
2320                 type: AV.Types.FUNCTION
2321             },
2322             {
2323                 name: 'errorCallback',
2324                 type: AV.Types.FUNCTION,
2325                 optional: true,
2326                 nullable: true
2327             }
2328         ]);
2329
2330         var callback = function(result) {
2331             if (native.isFailure(result)) {
2332                 native.callIfPossible(args.errorCallback, native.getErrorObject(result));
2333             } else {
2334                 var d = numberArrayToByteArray(native.getResultObject(result));
2335                 args.successCallback(d);
2336             }
2337         };
2338
2339         var callArgs = { handle: handle_, address: address_ };
2340
2341         var result = native.call(
2342             'BluetoothGATTClientServiceReadValue',
2343             callArgs,
2344             callback
2345         );
2346
2347         if (native.isFailure(result)) {
2348             throw native.getErrorObject(result);
2349         }
2350     };
2351
2352     this.writeValue = function(value, successCallback, errorCallback) {
2353         privUtils_.log('Entered BluetoothGATTCharacteristic.writeValue()');
2354         var args = AV.validateArgs(Array.prototype.slice.call(arguments, 1), [
2355             {
2356                 name: 'successCallback',
2357                 type: AV.Types.FUNCTION,
2358                 optional: true,
2359                 nullable: true
2360             },
2361             {
2362                 name: 'errorCallback',
2363                 type: AV.Types.FUNCTION,
2364                 optional: true,
2365                 nullable: true
2366             }
2367         ]);
2368
2369         var callback = function(result) {
2370             if (native.isFailure(result)) {
2371                 native.callIfPossible(args.errorCallback, native.getErrorObject(result));
2372             } else {
2373                 native.callIfPossible(args.successCallback);
2374             }
2375         };
2376
2377         var callArgs = {
2378             handle: handle_,
2379             value: BluetoothManager_toByteArray(value),
2380             address: address_
2381         };
2382
2383         var result = native.call(
2384             'BluetoothGATTClientServiceWriteValue',
2385             callArgs,
2386             callback
2387         );
2388
2389         if (native.isFailure(result)) {
2390             throw native.getErrorObject(result);
2391         }
2392     };
2393
2394     var addValueChangeListener = function() {
2395         privUtils_.log('Entered BluetoothGATTCharacteristic.addValueChangeListener()');
2396         privUtils_.checkPrivilegeAccess4Ver(
2397             '2.4',
2398             Privilege.BLUETOOTH,
2399             Privilege.BLUETOOTH_ADMIN
2400         );
2401         var args = AV.validateArgs(arguments, [
2402             {
2403                 name: 'callback',
2404                 type: AV.Types.FUNCTION
2405             }
2406         ]);
2407
2408         var callArgs = { handle: handle_, address: address_ };
2409
2410         var callback = function(event) {
2411             if (event.handle === handle_) {
2412                 args.callback(numberArrayToByteArray(native.getResultObject(event)));
2413             }
2414         };
2415
2416         return _bluetoothGATTCharacteristicListener.addListener(callback, callArgs);
2417     };
2418
2419     this.addValueChangeListener = function() {
2420         return addValueChangeListener.apply(this, arguments);
2421     };
2422
2423     this.removeValueChangeListener = function() {
2424         privUtils_.log('Entered BluetoothGATTCharacteristic.removeValueChangeListener()');
2425
2426         var args = AV.validateArgs(arguments, [
2427             {
2428                 name: 'watchID',
2429                 type: AV.Types.LONG
2430             }
2431         ]);
2432
2433         var callArgs = { handle: handle_, address: address_ };
2434
2435         return _bluetoothGATTCharacteristicListener.removeListener(
2436             args.watchID,
2437             callArgs
2438         );
2439     };
2440 };
2441
2442 var ValidateBluetoothGATTServerCharacteristicInit = function(initData) {
2443     return AV.validateArgs(
2444         [
2445             initData['uuid'],
2446             initData['descriptors'] || [],
2447             initData['isBroadcast'] || false,
2448             initData['hasExtendedProperties'] || false,
2449             initData['isNotify'] || false,
2450             initData['isIndication'] || false,
2451             initData['isReadable'] || false,
2452             initData['isSignedWrite'] || false,
2453             initData['isWritable'] || false,
2454             initData['isWriteNoResponse'] || false,
2455             initData['readPermission'] || false,
2456             initData['writePermission'] || false,
2457             initData['encryptedReadPermission'] || false,
2458             initData['encryptedWritePermission'] || false,
2459             initData['encryptedSignedReadPermission'] || false,
2460             initData['encryptedSignedWritePermission'] || false,
2461             initData['readValueRequestCallback'] || null,
2462             initData['readValueSendResponseSuccessCallback'] || null,
2463             initData['readValueSendResponseErrorCallback'] || null,
2464             initData['writeValueRequestCallback'] || null,
2465             initData['writeValueSendResponseSuccessCallback'] || null,
2466             initData['writeValueSendResponseErrorCallback'] || null
2467         ],
2468         [
2469             {
2470                 name: 'uuid',
2471                 type: AV.Types.STRING,
2472                 validator: IsUuidValid
2473             },
2474             {
2475                 name: 'descriptors',
2476                 type: AV.Types.ARRAY
2477             },
2478             {
2479                 name: 'isBroadcast',
2480                 type: AV.Types.BOOLEAN
2481             },
2482             {
2483                 name: 'hasExtendedProperties',
2484                 type: AV.Types.BOOLEAN
2485             },
2486             {
2487                 name: 'isNotify',
2488                 type: AV.Types.BOOLEAN
2489             },
2490             {
2491                 name: 'isIndication',
2492                 type: AV.Types.BOOLEAN
2493             },
2494             {
2495                 name: 'isReadable',
2496                 type: AV.Types.BOOLEAN
2497             },
2498             {
2499                 name: 'isSignedWrite',
2500                 type: AV.Types.BOOLEAN
2501             },
2502             {
2503                 name: 'isWritable',
2504                 type: AV.Types.BOOLEAN
2505             },
2506             {
2507                 name: 'isWriteNoResponse',
2508                 type: AV.Types.BOOLEAN
2509             },
2510             {
2511                 name: 'readPermission',
2512                 type: AV.Types.BOOLEAN
2513             },
2514             {
2515                 name: 'writePermission',
2516                 type: AV.Types.BOOLEAN
2517             },
2518             {
2519                 name: 'encryptedReadPermission',
2520                 type: AV.Types.BOOLEAN
2521             },
2522             {
2523                 name: 'encryptedWritePermission',
2524                 type: AV.Types.BOOLEAN
2525             },
2526             {
2527                 name: 'encryptedSignedReadPermission',
2528                 type: AV.Types.BOOLEAN
2529             },
2530             {
2531                 name: 'encryptedSignedWritePermission',
2532                 type: AV.Types.BOOLEAN
2533             },
2534             {
2535                 name: 'readValueRequestCallback',
2536                 type: AV.Types.FUNCTION,
2537                 nullable: true
2538             },
2539             {
2540                 name: 'readValueSendResponseSuccessCallback',
2541                 type: AV.Types.FUNCTION,
2542                 nullable: true
2543             },
2544             {
2545                 name: 'readValueSendResponseErrorCallback',
2546                 type: AV.Types.FUNCTION,
2547                 nullable: true
2548             },
2549             {
2550                 name: 'writeValueRequestCallback',
2551                 type: AV.Types.FUNCTION,
2552                 nullable: true
2553             },
2554             {
2555                 name: 'writeValueSendResponseErrorCallback',
2556                 type: AV.Types.FUNCTION,
2557                 nullable: true
2558             },
2559             {
2560                 name: 'writeValueSendResponseSuccessCallback',
2561                 type: AV.Types.FUNCTION,
2562                 nullable: true
2563             }
2564         ]
2565     );
2566 };
2567
2568 //class BluetoothGATTServerCharacteristic ///////////////////////////
2569 var BluetoothGATTServerCharacteristic = function(data) {
2570     data = ValidateBluetoothGATTServerCharacteristicInit(data);
2571
2572     BluetoothGATTCharacteristic.call(this, data, null);
2573
2574     var descriptors_ = data.descriptors.map(function(descriptor_data) {
2575         return new BluetoothGATTServerDescriptor(descriptor_data, null);
2576     });
2577
2578     Object.defineProperties(this, {
2579         descriptors: {
2580             enumerable: true,
2581             get: function() {
2582                 return descriptors_.slice();
2583             },
2584             set: function() {}
2585         },
2586         readPermission: {
2587             enumerable: true,
2588             get: function() {
2589                 return data.readPermission;
2590             },
2591             set: function() {}
2592         },
2593         writePermission: {
2594             enumerable: true,
2595             get: function() {
2596                 return data.writePermission;
2597             },
2598             set: function() {}
2599         },
2600         encryptedReadPermission: {
2601             enumerable: true,
2602             get: function() {
2603                 return data.encryptedReadPermission;
2604             },
2605             set: function() {}
2606         },
2607         encryptedWritePermission: {
2608             enumerable: true,
2609             get: function() {
2610                 return data.encryptedWritePermission;
2611             },
2612             set: function() {}
2613         },
2614         encryptedSignedReadPermission: {
2615             enumerable: true,
2616             get: function() {
2617                 return data.encryptedSignedReadPermission;
2618             },
2619             set: function() {}
2620         },
2621         encryptedSignedWritePermission: {
2622             enumerable: true,
2623             get: function() {
2624                 return data.encryptedSignedWritePermission;
2625             },
2626             set: function() {}
2627         },
2628         // This properties are "private" and meant not to be used by users
2629         _readValueRequestCallback: {
2630             enumerable: true,
2631             get: function() {
2632                 return data.readValueRequestCallback;
2633             },
2634             set: function() {}
2635         },
2636         _readValueSendResponseSuccessCallback: {
2637             enumerable: false,
2638             get: function() {
2639                 return data.readValueSendResponseSuccessCallback;
2640             },
2641             set: function() {}
2642         },
2643         _readValueSendResponseErrorCallback: {
2644             enumerable: false,
2645             get: function() {
2646                 return data.readValueSendResponseErrorCallback;
2647             },
2648             set: function() {}
2649         },
2650         _writeValueRequestCallback: {
2651             enumerable: false,
2652             get: function() {
2653                 return data.writeValueRequestCallback;
2654             },
2655             set: function() {}
2656         },
2657         _writeValueSendResponseSuccessCallback: {
2658             enumerable: false,
2659             get: function() {
2660                 return data.writeValueSendResponseSuccessCallback;
2661             },
2662             set: function() {}
2663         },
2664         _writeValueSendResponseErrorCallback: {
2665             enumerable: false,
2666             get: function() {
2667                 return data.writeValueSendResponseErrorCallback;
2668             },
2669             set: function() {}
2670         },
2671         _id: {
2672             // It has to be enumerable, to be serialized with JSON.stringify()
2673             enumerable: true,
2674             value: NextGattServerEntityID()
2675         }
2676     });
2677
2678     this.readValue = function() {
2679         throw new WebAPIException(
2680             'NotSupportedError',
2681             'This method cannot be called on BluetoothGATTServerCharacteristic'
2682         );
2683     };
2684
2685     this.writeValue = function() {
2686         throw new WebAPIException(
2687             'NotSupportedError',
2688             'This method cannot be called on BluetoothGATTServerCharacteristic'
2689         );
2690     };
2691
2692     this.addValueChangeListener = function() {
2693         throw new WebAPIException(
2694             'NotSupportedError',
2695             'This method cannot be called on BluetoothGATTServerCharacteristic'
2696         );
2697     };
2698
2699     this.removeValueChangeListener = function() {
2700         /* Intended no operation */
2701     };
2702
2703     this.notifyAboutValueChange = function(
2704         value,
2705         clientAddress,
2706         notificationCB,
2707         errorCB
2708     ) {
2709         var args = AV.validateArgs(Array.prototype.slice.call(arguments, 1), [
2710             {
2711                 name: 'clientAddress',
2712                 type: AV.Types.STRING,
2713                 optional: true,
2714                 nullable: true
2715             },
2716             {
2717                 name: 'notificationCB',
2718                 type: AV.Types.LISTENER,
2719                 values: [
2720                     'onnotificationsuccess',
2721                     'onnotificationfail',
2722                     'onnotificationfinish'
2723                 ],
2724                 optional: true,
2725                 nullable: true
2726             },
2727             {
2728                 name: 'errorCB',
2729                 type: AV.Types.FUNCTION,
2730                 optional: true,
2731                 nullable: true
2732             }
2733         ]);
2734
2735         var callArgs = {
2736             value: BluetoothManager_toByteArray(value),
2737             client: args.clientAddress || null,
2738             _id: this._id,
2739             notifyId: _BluetoothGATTServerCharacteristicNotifyId++
2740         };
2741
2742         var callback = function(result) {
2743             if (native.isFailure(result)) {
2744                 native.callIfPossible(args.errorCB, native.getErrorObject(result));
2745             }
2746         };
2747
2748         var result = native.call(
2749             'BluetoothGATTServerCharacteristicNotifyAboutValueChange',
2750             callArgs,
2751             callback
2752         );
2753
2754         if (native.isFailure(result)) {
2755             throw native.getErrorObject(result);
2756         }
2757
2758         native.addListener(
2759             'BluetoothGATTServerCharacteristicNotifyCallback_' + callArgs.notifyId,
2760             _BluetoothGATTServerCharacteristicNotifyCallback
2761         );
2762
2763         _BluetoothGATTServerCharacteristicNotifyListeners[callArgs.notifyId] =
2764             args.notificationCB;
2765     };
2766 };
2767
2768 BluetoothGATTServerCharacteristic.prototype = Object.create(
2769     BluetoothGATTCharacteristic.prototype
2770 );
2771
2772 Object.defineProperty(BluetoothGATTServerCharacteristic.prototype, 'constructor', {
2773     value: BluetoothGATTServerCharacteristic,
2774     enumerable: false,
2775     writable: true
2776 });
2777
2778 tizen.GATTRequestReply = function(statusCode, data) {
2779     AV.isConstructorCall(this, tizen.GATTRequestReply);
2780
2781     var statusCode_ = Converter.toLong(statusCode);
2782     var data_ = T.isNullOrUndefined(data) ? null : BluetoothManager_toByteArray(data);
2783
2784     Object.defineProperties(this, {
2785         statusCode: {
2786             enumerable: true,
2787             get: function() {
2788                 return statusCode_;
2789             },
2790             set: function(v) {
2791                 statusCode_ = Converter.toLong(v);
2792             }
2793         },
2794         data: {
2795             enumerable: true,
2796             get: function() {
2797                 return data_;
2798             },
2799             set: function(v) {
2800                 data_ = T.isNullOrUndefined(v) ? null : numberArrayToByteArray(v);
2801             }
2802         }
2803     });
2804 };
2805
2806 function _createReadValueRequestCallback(
2807     _id,
2808     sendResponseSuccessCallback,
2809     sendResponseErrorCallback
2810 ) {
2811     return _singleListenerBuilder(
2812         'ReadValueRequestCallback_' + _id,
2813         /*
2814          * _singleListenerBuilder requires 2 callbacks, the second of which
2815          * is an error callback.
2816          * Read value request events coming from the native layer
2817          * are never errors.
2818          * Hence, we don't use the second callback here.
2819          */
2820         function(event, readValueRequestCallback, unusedErrorCallback) {
2821             var clientAddress = event.clientAddress;
2822             var offset = event.offset;
2823
2824             if (readValueRequestCallback) {
2825                 var requestReply = readValueRequestCallback(clientAddress, offset);
2826                 var response = {
2827                     _id: _id,
2828                     requestId: event.requestId,
2829                     requestType: event.requestType,
2830                     offset: offset,
2831                     statusCode: requestReply.statusCode,
2832                     data: requestReply.data
2833                 };
2834                 var callback = function(result) {
2835                     if (native.isFailure(result)) {
2836                         native.callIfPossible(
2837                             sendResponseErrorCallback,
2838                             native.getErrorObject(result)
2839                         );
2840                     } else {
2841                         native.callIfPossible(sendResponseSuccessCallback);
2842                     }
2843                 };
2844                 var result = native.call(
2845                     'BluetoothGATTServerSendResponse',
2846                     response,
2847                     callback
2848                 );
2849             }
2850             return true;
2851         }
2852     );
2853 }
2854
2855 function _createWriteValueRequestCallback(
2856     _id,
2857     sendResponseSuccessCallback,
2858     sendResponseErrorCallback
2859 ) {
2860     return _singleListenerBuilder(
2861         'WriteValueRequestCallback_' + _id,
2862         /*
2863          * _singleListenerBuilder requires 2 callbacks, the second of which
2864          * is an error callback.
2865          * Write value request events coming from the native layer
2866          * are never errors.
2867          * Hence, we don't use the second callback here.
2868          */
2869         function(event, writeValueRequestCallback, unusedErrorCallback) {
2870             var clientAddress = event.clientAddress;
2871             var value = event.value;
2872             var offset = event.offset;
2873             var replyRequired = event.replyRequired;
2874
2875             if (writeValueRequestCallback) {
2876                 var requestReply = writeValueRequestCallback(
2877                     clientAddress,
2878                     BluetoothManager_toByteArray(value),
2879                     offset,
2880                     replyRequired
2881                 );
2882                 var response = {
2883                     _id: _id,
2884                     requestId: event.requestId,
2885                     requestType: event.requestType,
2886                     value: null, // Responses to write requests don't contain value
2887                     offset: offset,
2888                     statusCode: requestReply.statusCode,
2889                     data: requestReply.data
2890                 };
2891                 var callback = function(result) {
2892                     if (native.isFailure(result)) {
2893                         native.callIfPossible(
2894                             sendResponseErrorCallback,
2895                             native.getErrorObject(result)
2896                         );
2897                     } else {
2898                         native.callIfPossible(sendResponseSuccessCallback);
2899                     }
2900                 };
2901                 var result = native.call(
2902                     'BluetoothGATTServerSendResponse',
2903                     response,
2904                     callback
2905                 );
2906             }
2907             return true;
2908         }
2909     );
2910 }
2911
2912 var _BluetoothGATTServerReadWriteValueRequestCallbacks = {};
2913
2914 var _setReadValueRequestCallbackCommon = function() {
2915     var args = AV.validateArgs(arguments, [
2916         {
2917             name: 'readValueRequestCallback',
2918             type: AV.Types.FUNCTION
2919         },
2920         {
2921             name: 'successCallback',
2922             type: AV.Types.FUNCTION,
2923             optional: true,
2924             nullable: true
2925         },
2926         {
2927             name: 'errorCallback',
2928             type: AV.Types.FUNCTION,
2929             optional: true,
2930             nullable: true
2931         },
2932         {
2933             name: 'sendResponseSuccessCallback',
2934             type: AV.Types.FUNCTION,
2935             optional: true,
2936             nullable: true
2937         },
2938         {
2939             name: 'sendResponseErrorCallback',
2940             type: AV.Types.FUNCTION,
2941             optional: true,
2942             nullable: true
2943         }
2944     ]);
2945
2946     var entityId = this._id;
2947
2948     var callback = function(result) {
2949         if (native.isFailure(result)) {
2950             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
2951         } else {
2952             var readValueRequestCallback = _createReadValueRequestCallback(
2953                 entityId,
2954                 args.sendResponseSuccessCallback,
2955                 args.sendResponseErrorCallback
2956             );
2957             readValueRequestCallback.addListener(
2958                 args.readValueRequestCallback,
2959                 function unusedErrorCallback() {
2960                     /* Intentionally no operation */
2961                 }
2962             );
2963             _BluetoothGATTServerReadWriteValueRequestCallbacks[
2964                 'ReadValueCallback' + entityId
2965             ] = readValueRequestCallback;
2966             native.callIfPossible(args.successCallback, native.getErrorObject(result));
2967         }
2968     };
2969
2970     var callArgs = { _id: this._id };
2971     var result = native.call(
2972         'BluetoothGATTServerSetReadValueRequestCallback',
2973         callArgs,
2974         callback
2975     );
2976
2977     if (native.isFailure(result)) {
2978         throw native.getErrorObject(result);
2979     }
2980 };
2981
2982 var _setWriteValueRequestCallbackCommon = function() {
2983     var args = AV.validateArgs(arguments, [
2984         {
2985             name: 'writeValueRequestCallback',
2986             type: AV.Types.FUNCTION
2987         },
2988         {
2989             name: 'successCallback',
2990             type: AV.Types.FUNCTION,
2991             optional: true,
2992             nullable: true
2993         },
2994         {
2995             name: 'errorCallback',
2996             type: AV.Types.FUNCTION,
2997             optional: true,
2998             nullable: true
2999         },
3000         {
3001             name: 'sendResponseSuccessCallback',
3002             type: AV.Types.FUNCTION,
3003             optional: true,
3004             nullable: true
3005         },
3006         {
3007             name: 'sendResponseErrorCallback',
3008             type: AV.Types.FUNCTION,
3009             optional: true,
3010             nullable: true
3011         }
3012     ]);
3013
3014     var entityId = this._id;
3015
3016     var callback = function(result) {
3017         if (native.isFailure(result)) {
3018             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3019         } else {
3020             var writeValueRequestCallback = _createWriteValueRequestCallback(
3021                 entityId,
3022                 args.sendResponseSuccessCallback,
3023                 args.sendResponseErrorCallback
3024             );
3025             writeValueRequestCallback.addListener(
3026                 args.writeValueRequestCallback,
3027                 function unusedErrorCallback() {
3028                     /* Intentionally no operation */
3029                 }
3030             );
3031             _BluetoothGATTServerReadWriteValueRequestCallbacks[
3032                 'WriteValueCallback' + entityId
3033             ] = writeValueRequestCallback;
3034             native.callIfPossible(args.successCallback, native.getErrorObject(result));
3035         }
3036     };
3037
3038     var callArgs = { _id: this._id };
3039     var result = native.call(
3040         'BluetoothGATTServerSetWriteValueRequestCallback',
3041         callArgs,
3042         callback
3043     );
3044
3045     if (native.isFailure(result)) {
3046         throw native.getErrorObject(result);
3047     }
3048 };
3049
3050 BluetoothGATTServerCharacteristic.prototype.setReadValueRequestCallback = _setReadValueRequestCallbackCommon;
3051 BluetoothGATTServerCharacteristic.prototype.setWriteValueRequestCallback = _setWriteValueRequestCallbackCommon;
3052
3053 var _BluetoothGATTServerCharacteristicNotifyId = 0;
3054 var _BluetoothGATTServerCharacteristicNotifyListeners = {};
3055 var _BluetoothGATTServerCharacteristicNotifyCallback = function(event) {
3056     privUtils_.log('Got notification about characteristic\'s value change');
3057     if (
3058         T.isNullOrUndefined(
3059             _BluetoothGATTServerCharacteristicNotifyListeners[event.notifyId]
3060         )
3061     ) {
3062         privUtils_.log('Notification callback is not set, skipping');
3063         return;
3064     }
3065
3066     var callback = _BluetoothGATTServerCharacteristicNotifyListeners[event.notifyId];
3067
3068     if (event.type === 'onnotificationsuccess') {
3069         native.callIfPossible(callback.onnotificationsuccess, event.clientAddress);
3070         return;
3071     }
3072
3073     if (event.type === 'onnotificationfail') {
3074         native.callIfPossible(
3075             callback.onnotificationfail,
3076             event.clientAddress,
3077             native.getErrorObject(event)
3078         );
3079         return;
3080     }
3081
3082     if (event.type === 'onnotificationfinish') {
3083         native.callIfPossible(callback.onnotificationfinish, event.clientAddress);
3084     }
3085
3086     native.removeListener(
3087         'BluetoothGATTServerCharacteristicNotifyCallback_' + event.notifyId
3088     );
3089 };
3090
3091 /**
3092  * Creates a manager for specified listener event. Manager handles multiple
3093  * registered listeners
3094  *
3095  * @param {string} name - name of the listener this manager handles
3096  * @param {function} callback - function to be invoked when event specified by the name
3097  *                              fires.
3098  *                              This function should have following signature:
3099  *                              void callback(listener, event);
3100  * @param {string} addListenerId - optional parameter. If specified, this native
3101  *                                 method will be called synchronously when
3102  *                                 listener is added.
3103  * @param {string} removeListenerId - optional parameter. If specified, this native
3104  *                                 method will be called synchronously when
3105  *                                 listener is removed.
3106  * @param {bool} repeatNativeCall - optional parameter. If specified, the addListenerId
3107  *                                 and removeListenerId methods will be called
3108  *                                 synchronously each time listener is added/removed.
3109  *                                 Otherwise they are going to be called just once: when
3110  *                                 first listener is added and last listener is removed.
3111  *
3112  * @return {object} object which allows to add or remove callbacks for specified listener
3113  */
3114 function _multipleListenerBuilder(
3115     name,
3116     callback,
3117     addListenerId,
3118     removeListenerId,
3119     repeatNativeCall
3120 ) {
3121     var listenerName = name;
3122     var addId = addListenerId;
3123     var removeId = removeListenerId;
3124     var callbackFunction = callback;
3125     var listeners = {};
3126     var nextId = 1;
3127     var jsListenerRegistered = false;
3128     var nativeListenerRegistered = false;
3129     var repeatNativeListenerCall = repeatNativeCall;
3130
3131     function innerCallback(event) {
3132         for (var watchId in listeners) {
3133             if (listeners.hasOwnProperty(watchId)) {
3134                 callbackFunction(listeners[watchId], event);
3135             }
3136         }
3137     }
3138
3139     function addListener(callback, args) {
3140         var id = ++nextId;
3141
3142         if (addId && (!nativeListenerRegistered || repeatNativeListenerCall)) {
3143             var result = native.callSync(addId, args || {});
3144             if (native.isFailure(result)) {
3145                 throw native.getErrorObject(result);
3146             }
3147             nativeListenerRegistered = true;
3148         }
3149
3150         if (!jsListenerRegistered) {
3151             native.addListener(listenerName, innerCallback);
3152             jsListenerRegistered = true;
3153         }
3154
3155         listeners[id] = callback;
3156         return id;
3157     }
3158
3159     function removeListener(watchId, args) {
3160         if (listeners.hasOwnProperty(watchId)) {
3161             delete listeners[watchId];
3162         }
3163
3164         if (
3165             removeId &&
3166             ((nativeListenerRegistered && T.isEmptyObject(listeners)) ||
3167                 repeatNativeListenerCall)
3168         ) {
3169             var result = native.callSync(removeId, args || {});
3170             if (native.isFailure(result)) {
3171                 throw native.getErrorObject(result);
3172             }
3173             nativeListenerRegistered = false;
3174         }
3175
3176         if (jsListenerRegistered && T.isEmptyObject(listeners)) {
3177             native.removeListener(listenerName, innerCallback);
3178             jsListenerRegistered = false;
3179         }
3180     }
3181
3182     return {
3183         addListener: addListener,
3184         removeListener: removeListener
3185     };
3186 }
3187
3188 var _bluetoothGATTCharacteristicListener = _multipleListenerBuilder(
3189     'BluetoothGATTCharacteristicValueChangeListener',
3190     function(listener, event) {
3191         listener(event);
3192     },
3193     'BluetoothGATTClientServiceAddValueChangeListener',
3194     'BluetoothGATTClientServiceRemoveValueChangeListener',
3195     true
3196 );
3197
3198 /*
3199  * This object is used by:
3200  * - BluetoothLEDevice.addConnectStateChangeListener()
3201  * - BluetoothLEAdapter.addConnectStateChangeListener()
3202  */
3203 var _bleConnectChangeListener = _multipleListenerBuilder(
3204     'BluetoothLEConnectChangeCallback',
3205     function(listener, event) {
3206         listener(event);
3207     },
3208     'BluetoothLEDeviceAddConnectStateChangeListener',
3209     'BluetoothLEDeviceRemoveConnectStateChangeListener'
3210 );
3211
3212 var _bleAttMtuChangeListener = _multipleListenerBuilder(
3213     'BluetoothLEAttMtuChangeCallback',
3214     function(listener, event) {
3215         listener(event);
3216     },
3217     'BluetoothLEDeviceAddAttMtuChangeListener',
3218     'BluetoothLEDeviceRemoveAttMtuChangeListener'
3219 );
3220
3221 //class BluetoothGATTDescriptor ///////////////////////////
3222 var BluetoothGATTDescriptor = function(data, address) {
3223     var handle_ = data.handle;
3224     //address_ is needed to control if device is still connected
3225     var address_ = address;
3226     var uuid_ = data.uuid;
3227
3228     this.readValue = function() {
3229         privUtils_.log('Entered BluetoothGATTDescriptor.readValue()');
3230         var args = AV.validateArgs(arguments, [
3231             {
3232                 name: 'successCallback',
3233                 type: AV.Types.FUNCTION
3234             },
3235             {
3236                 name: 'errorCallback',
3237                 type: AV.Types.FUNCTION,
3238                 optional: true,
3239                 nullable: true
3240             }
3241         ]);
3242
3243         var callback = function(result) {
3244             if (native.isFailure(result)) {
3245                 native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3246             } else {
3247                 var d = numberArrayToByteArray(native.getResultObject(result));
3248                 args.successCallback(d);
3249             }
3250         };
3251
3252         var callArgs = { handle: handle_, address: address_ };
3253
3254         var result = native.call(
3255             'BluetoothGATTClientServiceReadValue',
3256             callArgs,
3257             callback
3258         );
3259
3260         if (native.isFailure(result)) {
3261             throw native.getErrorObject(result);
3262         }
3263     };
3264
3265     this.writeValue = function(value, successCallback, errorCallback) {
3266         privUtils_.log('Entered BluetoothGATTDescriptor.writeValue()');
3267         var args = AV.validateArgs(Array.prototype.slice.call(arguments, 1), [
3268             {
3269                 name: 'successCallback',
3270                 type: AV.Types.FUNCTION,
3271                 optional: true,
3272                 nullable: true
3273             },
3274             {
3275                 name: 'errorCallback',
3276                 type: AV.Types.FUNCTION,
3277                 optional: true,
3278                 nullable: true
3279             }
3280         ]);
3281
3282         var callback = function(result) {
3283             if (native.isFailure(result)) {
3284                 native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3285             } else {
3286                 native.callIfPossible(args.successCallback);
3287             }
3288         };
3289
3290         var callArgs = {
3291             handle: handle_,
3292             value: BluetoothManager_toByteArray(value),
3293             address: address_
3294         };
3295
3296         var result = native.call(
3297             'BluetoothGATTClientServiceWriteValue',
3298             callArgs,
3299             callback
3300         );
3301
3302         if (native.isFailure(result)) {
3303             throw native.getErrorObject(result);
3304         }
3305     };
3306
3307     Object.defineProperties(this, {
3308         uuid: {
3309             enumerable: true,
3310             get: function() {
3311                 return uuid_;
3312             },
3313             set: function() {}
3314         }
3315     });
3316 };
3317
3318 var ValidateBluetoothGATTServerDescriptorInit = function(initData) {
3319     return AV.validateArgs(
3320         [
3321             initData['uuid'],
3322             initData['readPermission'] || false,
3323             initData['writePermission'] || false,
3324             initData['encryptedReadPermission'] || false,
3325             initData['encryptedWritePermission'] || false,
3326             initData['encryptedSignedReadPermission'] || false,
3327             initData['encryptedSignedWritePermission'] || false,
3328             initData['readValueRequestCallback'] || null,
3329             initData['readValueSendResponseSuccessCallback'] || null,
3330             initData['readValueSendResponseErrorCallback'] || null,
3331             initData['writeValueRequestCallback'] || null,
3332             initData['writeValueSendResponseSuccessCallback'] || null,
3333             initData['writeValueSendResponseErrorCallback'] || null
3334         ],
3335         [
3336             {
3337                 name: 'uuid',
3338                 type: AV.Types.STRING,
3339                 validator: IsUuidValid
3340             },
3341             {
3342                 name: 'readPermission',
3343                 type: AV.Types.BOOLEAN
3344             },
3345             {
3346                 name: 'writePermission',
3347                 type: AV.Types.BOOLEAN
3348             },
3349             {
3350                 name: 'encryptedReadPermission',
3351                 type: AV.Types.BOOLEAN
3352             },
3353             {
3354                 name: 'encryptedWritePermission',
3355                 type: AV.Types.BOOLEAN
3356             },
3357             {
3358                 name: 'encryptedSignedReadPermission',
3359                 type: AV.Types.BOOLEAN
3360             },
3361             {
3362                 name: 'encryptedSignedWritePermission',
3363                 type: AV.Types.BOOLEAN
3364             },
3365             {
3366                 name: 'readValueRequestCallback',
3367                 type: AV.Types.FUNCTION,
3368                 nullable: true
3369             },
3370             {
3371                 name: 'readValueSendResponseSuccessCallback',
3372                 type: AV.Types.FUNCTION,
3373                 nullable: true
3374             },
3375             {
3376                 name: 'readValueSendResponseErrorCallback',
3377                 type: AV.Types.FUNCTION,
3378                 nullable: true
3379             },
3380             {
3381                 name: 'writeValueRequestCallback',
3382                 type: AV.Types.FUNCTION,
3383                 nullable: true
3384             },
3385             {
3386                 name: 'writeValueSendResponseErrorCallback',
3387                 type: AV.Types.FUNCTION,
3388                 nullable: true
3389             },
3390             {
3391                 name: 'writeValueSendResponseSuccessCallback',
3392                 type: AV.Types.FUNCTION,
3393                 nullable: true
3394             }
3395         ]
3396     );
3397 };
3398
3399 var BluetoothGATTServerDescriptor = function(data, address) {
3400     data = ValidateBluetoothGATTServerDescriptorInit(data);
3401
3402     BluetoothGATTDescriptor.call(this, data, null);
3403
3404     Object.defineProperties(this, {
3405         readPermission: {
3406             enumerable: true,
3407             get: function() {
3408                 return data.readPermission;
3409             },
3410             set: function() {}
3411         },
3412         writePermission: {
3413             enumerable: true,
3414             get: function() {
3415                 return data.writePermission;
3416             },
3417             set: function() {}
3418         },
3419         encryptedReadPermission: {
3420             enumerable: true,
3421             get: function() {
3422                 return data.encryptedReadPermission;
3423             },
3424             set: function() {}
3425         },
3426         encryptedWritePermission: {
3427             enumerable: true,
3428             get: function() {
3429                 return data.encryptedWritePermission;
3430             },
3431             set: function() {}
3432         },
3433         encryptedSignedReadPermission: {
3434             enumerable: true,
3435             get: function() {
3436                 return data.encryptedSignedReadPermission;
3437             },
3438             set: function() {}
3439         },
3440         encryptedSignedWritePermission: {
3441             enumerable: true,
3442             get: function() {
3443                 return data.encryptedSignedWritePermission;
3444             },
3445             set: function() {}
3446         },
3447         // This properties are "private" and meant not to be used by users
3448         _readValueRequestCallback: {
3449             enumerable: false,
3450             get: function() {
3451                 return data.readValueRequestCallback;
3452             },
3453             set: function() {}
3454         },
3455         _readValueSendResponseSuccessCallback: {
3456             enumerable: false,
3457             get: function() {
3458                 return data.readValueSendResponseSuccessCallback;
3459             },
3460             set: function() {}
3461         },
3462         _readValueSendResponseErrorCallback: {
3463             enumerable: false,
3464             get: function() {
3465                 return data.readValueSendResponseErrorCallback;
3466             },
3467             set: function() {}
3468         },
3469         _writeValueRequestCallback: {
3470             enumerable: false,
3471             get: function() {
3472                 return data.writeValueRequestCallback;
3473             },
3474             set: function() {}
3475         },
3476         _writeValueSendResponseSuccessCallback: {
3477             enumerable: false,
3478             get: function() {
3479                 return data.writeValueSendResponseSuccessCallback;
3480             },
3481             set: function() {}
3482         },
3483         _writeValueSendResponseErrorCallback: {
3484             enumerable: false,
3485             get: function() {
3486                 return data.writeValueSendResponseErrorCallback;
3487             },
3488             set: function() {}
3489         },
3490         _id: {
3491             // It has to be enumerable, to be serialized with JSON.stringify()
3492             enumerable: true,
3493             value: NextGattServerEntityID()
3494         }
3495     });
3496
3497     this.readValue = function() {
3498         throw new WebAPIException(
3499             'NotSupportedError',
3500             'This method cannot be called on BluetoothGATTServerDescriptor'
3501         );
3502     };
3503
3504     this.writeValue = function() {
3505         throw new WebAPIException(
3506             'NotSupportedError',
3507             'This method cannot be called on BluetoothGATTServerDescriptor'
3508         );
3509     };
3510 };
3511
3512 BluetoothGATTServerDescriptor.prototype = Object.create(
3513     BluetoothGATTDescriptor.prototype
3514 );
3515
3516 Object.defineProperty(BluetoothGATTServerDescriptor.prototype, 'constructor', {
3517     value: BluetoothGATTServerDescriptor,
3518     enumerable: false,
3519     writable: true
3520 });
3521
3522 BluetoothGATTServerDescriptor.prototype.setReadValueRequestCallback = _setReadValueRequestCallbackCommon;
3523 BluetoothGATTServerDescriptor.prototype.setWriteValueRequestCallback = _setWriteValueRequestCallbackCommon;
3524
3525 // class BluetoothAdapter ///////////////////////////
3526 var BluetoothAdapter = function() {
3527     function nameGetter() {
3528         var result = native.callSync('BluetoothAdapterGetName', {});
3529
3530         if (native.isFailure(result)) {
3531             return '';
3532         } else {
3533             return native.getResultObject(result);
3534         }
3535     }
3536
3537     function addressGetter() {
3538         var result = native.callSync('BluetoothAdapterGetAddress', {});
3539
3540         if (native.isFailure(result)) {
3541             return '';
3542         } else {
3543             return native.getResultObject(result);
3544         }
3545     }
3546
3547     function poweredGetter() {
3548         var result = native.callSync('BluetoothAdapterGetPowered', {});
3549
3550         if (native.isFailure(result)) {
3551             return false;
3552         } else {
3553             return native.getResultObject(result);
3554         }
3555     }
3556
3557     function visibleGetter() {
3558         var result = native.callSync('BluetoothAdapterGetVisible', {});
3559
3560         if (native.isFailure(result)) {
3561             return false;
3562         } else {
3563             return native.getResultObject(result);
3564         }
3565     }
3566
3567     Object.defineProperties(this, {
3568         name: {
3569             enumerable: true,
3570             set: function() {},
3571             get: nameGetter
3572         },
3573         address: {
3574             enumerable: true,
3575             set: function() {},
3576             get: addressGetter
3577         },
3578         powered: {
3579             enumerable: true,
3580             set: function() {},
3581             get: poweredGetter
3582         },
3583         visible: {
3584             enumerable: true,
3585             set: function() {},
3586             get: visibleGetter
3587         }
3588     });
3589 };
3590
3591 BluetoothAdapter.prototype.setName = function() {
3592     privUtils_.log('Entered BluetoothAdapter.setName()');
3593     var args = AV.validateArgs(arguments, [
3594         {
3595             name: 'name',
3596             type: AV.Types.STRING
3597         },
3598         {
3599             name: 'successCallback',
3600             type: AV.Types.FUNCTION,
3601             optional: true,
3602             nullable: true
3603         },
3604         {
3605             name: 'errorCallback',
3606             type: AV.Types.FUNCTION,
3607             optional: true,
3608             nullable: true
3609         }
3610     ]);
3611
3612     var callArgs = {
3613         name: args.name
3614     };
3615
3616     var callback = function(result) {
3617         if (native.isFailure(result)) {
3618             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3619         } else {
3620             native.callIfPossible(args.successCallback);
3621         }
3622     };
3623
3624     var result = native.call('BluetoothAdapterSetName', callArgs, callback);
3625     if (native.isFailure(result)) {
3626         throw native.getErrorObject(result);
3627     }
3628 };
3629
3630 BluetoothAdapter.prototype.setPowered = function() {
3631     privUtils_.log('Entered BluetoothAdapter.setPowered()');
3632     privUtils_.printDeprecationWarningFor('setPowered()');
3633     privUtils_.warn(
3634         'Let the user turn on/off Bluetooth through the Settings application instead.'
3635     );
3636
3637     var args = AV.validateArgs(arguments, [
3638         {
3639             name: 'powered',
3640             type: AV.Types.BOOLEAN
3641         },
3642         {
3643             name: 'successCallback',
3644             type: AV.Types.FUNCTION,
3645             optional: true,
3646             nullable: true
3647         },
3648         {
3649             name: 'errorCallback',
3650             type: AV.Types.FUNCTION,
3651             optional: true,
3652             nullable: true
3653         }
3654     ]);
3655
3656     var callArgs = {
3657         powered: args.powered
3658     };
3659
3660     var callback = function(result) {
3661         if (native.isFailure(result)) {
3662             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3663         } else {
3664             native.callIfPossible(args.successCallback);
3665         }
3666     };
3667
3668     var result = native.call('BluetoothAdapterSetPowered', callArgs, callback);
3669     if (native.isFailure(result)) {
3670         throw native.getErrorObject(result);
3671     }
3672 };
3673
3674 // This method is deprecated since Tizen 2.3.
3675 BluetoothAdapter.prototype.setVisible = function() {
3676     privUtils_.log('Entered BluetoothAdapter.setVisible()');
3677     privUtils_.printDeprecationWarningFor('setVisible()');
3678     privUtils_.warn(
3679         'Let the user change the Bluetooth visibility through the Settings ' +
3680             'application instead.'
3681     );
3682
3683     var args = AV.validateArgs(arguments, [
3684         {
3685             name: 'visible',
3686             type: AV.Types.BOOLEAN
3687         },
3688         {
3689             name: 'successCallback',
3690             type: AV.Types.FUNCTION,
3691             optional: true,
3692             nullable: true
3693         },
3694         {
3695             name: 'errorCallback',
3696             type: AV.Types.FUNCTION,
3697             optional: true,
3698             nullable: true
3699         },
3700         {
3701             name: 'timeout',
3702             type: AV.Types.UNSIGNED_LONG,
3703             optional: true,
3704             nullable: true
3705         }
3706     ]);
3707
3708     var callArgs = {
3709         visible: args.visible
3710     };
3711
3712     if (args.visible === true) {
3713         if (T.isNullOrUndefined(args.timeout)) {
3714             callArgs.timeout = 0;
3715         } else {
3716             callArgs.timeout = args.timeout > 65535 ? 180 : args.timeout;
3717         }
3718     }
3719
3720     var callback = function(result) {
3721         if (native.isFailure(result)) {
3722             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3723         } else {
3724             native.callIfPossible(args.successCallback);
3725         }
3726     };
3727
3728     var result = native.call('BluetoothAdapterSetVisible', callArgs, callback);
3729     if (native.isFailure(result)) {
3730         throw native.getErrorObject(result);
3731     }
3732 };
3733
3734 var _listener;
3735
3736 function _BluetoothAdapterChangeCallback(event) {
3737     privUtils_.log('_BluetoothAdapterChangeCallback');
3738
3739     var e = event;
3740     var d;
3741
3742     switch (e.action) {
3743     case 'onstatechanged':
3744         d = e.powered;
3745         break;
3746
3747     case 'onnamechanged':
3748         d = e.name;
3749         break;
3750
3751     case 'onvisibilitychanged':
3752         d = e.visible;
3753         break;
3754
3755     default:
3756         privUtils_.log('Unknown mode: ' + e.action);
3757         return;
3758     }
3759
3760     if (_listener[e.action]) {
3761         _listener[e.action](d);
3762     }
3763 }
3764
3765 BluetoothAdapter.prototype.setChangeListener = function() {
3766     privUtils_.log('Entered BluetoothAdapter.setChangeListener()');
3767     var args = AV.validateArgs(arguments, [
3768         {
3769             name: 'changeCallback',
3770             type: AV.Types.LISTENER,
3771             values: ['onstatechanged', 'onnamechanged', 'onvisibilitychanged']
3772         }
3773     ]);
3774
3775     if (T.isNullOrUndefined(_listener)) {
3776         native.addListener(
3777             'BluetoothAdapterChangeCallback',
3778             _BluetoothAdapterChangeCallback
3779         );
3780         native.callSync('BluetoothAdapterSetChangeListener', {});
3781     }
3782     _listener = args.changeCallback;
3783 };
3784
3785 BluetoothAdapter.prototype.unsetChangeListener = function() {
3786     privUtils_.log('Entered BluetoothAdapter.unsetChangeListener()');
3787     if (!T.isNullOrUndefined(_listener)) {
3788         native.removeListener(
3789             'BluetoothAdapterChangeCallback',
3790             _BluetoothAdapterChangeCallback
3791         );
3792         native.callSync('BluetoothAdapterUnsetChangeListener', {});
3793         _listener = undefined;
3794     }
3795 };
3796
3797 var _discoverDevicesSuccessCallback;
3798 var _discoverDevicesErrorCallback;
3799
3800 function _BluetoothDiscoverDevicesSuccessCallback(event) {
3801     var e = event;
3802     var d = null;
3803
3804     switch (e.action) {
3805     case 'onstarted':
3806         break;
3807
3808     case 'ondevicefound':
3809         d = new BluetoothDevice(e.data);
3810         break;
3811
3812     case 'ondevicedisappeared':
3813         d = e.data;
3814         break;
3815
3816     case 'onfinished':
3817         var result = e.data;
3818         d = [];
3819         result.forEach(function(data) {
3820             d.push(new BluetoothDevice(data));
3821         });
3822
3823         //remove listeners after discovering
3824         native.removeListener(
3825             'BluetoothDiscoverDevicesSuccessCallback',
3826             _BluetoothDiscoverDevicesSuccessCallback
3827         );
3828         native.removeListener(
3829             'BluetoothDiscoverDevicesErrorCallback',
3830             _BluetoothDiscoverDevicesErrorCallback
3831         );
3832         break;
3833
3834     default:
3835         privUtils_.log('Unknown mode: ' + e.action);
3836         return;
3837     }
3838
3839     if (_discoverDevicesSuccessCallback[e.action]) {
3840         _discoverDevicesSuccessCallback[e.action](d);
3841     }
3842 }
3843
3844 function _BluetoothDiscoverDevicesErrorCallback(event) {
3845     var e = event;
3846     setTimeout(function() {
3847         native.callIfPossible(_discoverDevicesErrorCallback, native.getErrorObject(e));
3848     }, 0);
3849 }
3850
3851 BluetoothAdapter.prototype.discoverDevices = function() {
3852     privUtils_.log('Entered BluetoothAdapter.discoverDevices()');
3853     var args = AV.validateArgs(arguments, [
3854         {
3855             name: 'successCallback',
3856             type: AV.Types.LISTENER,
3857             values: ['onstarted', 'ondevicefound', 'ondevicedisappeared', 'onfinished']
3858         },
3859         {
3860             name: 'errorCallback',
3861             type: AV.Types.FUNCTION,
3862             optional: true,
3863             nullable: true
3864         }
3865     ]);
3866
3867     _discoverDevicesSuccessCallback = args.successCallback;
3868     _discoverDevicesErrorCallback = args.errorCallback;
3869     native.addListener(
3870         'BluetoothDiscoverDevicesSuccessCallback',
3871         _BluetoothDiscoverDevicesSuccessCallback
3872     );
3873     native.addListener(
3874         'BluetoothDiscoverDevicesErrorCallback',
3875         _BluetoothDiscoverDevicesErrorCallback
3876     );
3877
3878     var result = native.callSync('BluetoothAdapterDiscoverDevices', {});
3879
3880     if (native.isFailure(result)) {
3881         native.removeListener(
3882             'BluetoothDiscoverDevicesSuccessCallback',
3883             _BluetoothDiscoverDevicesSuccessCallback
3884         );
3885         native.removeListener(
3886             'BluetoothDiscoverDevicesErrorCallback',
3887             _BluetoothDiscoverDevicesErrorCallback
3888         );
3889         throw native.getErrorObject(result);
3890     }
3891 };
3892
3893 BluetoothAdapter.prototype.stopDiscovery = function() {
3894     privUtils_.log('Entered BluetoothAdapter.stopDiscovery()');
3895     var args = AV.validateArgs(arguments, [
3896         {
3897             name: 'successCallback',
3898             type: AV.Types.FUNCTION,
3899             optional: true,
3900             nullable: true
3901         },
3902         {
3903             name: 'errorCallback',
3904             type: AV.Types.FUNCTION,
3905             optional: true,
3906             nullable: true
3907         }
3908     ]);
3909
3910     var callback = function(result) {
3911         if (native.isFailure(result)) {
3912             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3913         } else {
3914             native.callIfPossible(args.successCallback);
3915         }
3916     };
3917
3918     var result = native.call('BluetoothAdapterStopDiscovery', {}, callback);
3919     if (native.isFailure(result)) {
3920         throw native.getErrorObject(result);
3921     }
3922 };
3923
3924 BluetoothAdapter.prototype.getKnownDevices = function() {
3925     privUtils_.log('Entered BluetoothAdapter.getKnownDevices()');
3926     var args = AV.validateArgs(arguments, [
3927         {
3928             name: 'successCallback',
3929             type: AV.Types.FUNCTION
3930         },
3931         {
3932             name: 'errorCallback',
3933             type: AV.Types.FUNCTION,
3934             optional: true,
3935             nullable: true
3936         }
3937     ]);
3938
3939     var callback = function(result) {
3940         if (native.isFailure(result)) {
3941             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3942         } else {
3943             var r = native.getResultObject(result).devices;
3944             var devices = [];
3945             r.forEach(function(data) {
3946                 devices.push(new BluetoothDevice(data));
3947             });
3948             args.successCallback(devices);
3949         }
3950     };
3951
3952     var result = native.call('BluetoothAdapterGetKnownDevices', {}, callback);
3953     if (native.isFailure(result)) {
3954         throw native.getErrorObject(result);
3955     }
3956 };
3957
3958 BluetoothAdapter.prototype.getDevice = function() {
3959     privUtils_.log('Entered BluetoothAdapter.getDevice()');
3960     var args = AV.validateArgs(arguments, [
3961         {
3962             name: 'address',
3963             type: AV.Types.STRING
3964         },
3965         {
3966             name: 'successCallback',
3967             type: AV.Types.FUNCTION
3968         },
3969         {
3970             name: 'errorCallback',
3971             type: AV.Types.FUNCTION,
3972             optional: true,
3973             nullable: true
3974         }
3975     ]);
3976
3977     var callback = function(result) {
3978         if (native.isFailure(result)) {
3979             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
3980         } else {
3981             args.successCallback(new BluetoothDevice(native.getResultObject(result)));
3982         }
3983     };
3984
3985     var result = native.call(
3986         'BluetoothAdapterGetDevice',
3987         { address: args.address },
3988         callback
3989     );
3990     if (native.isFailure(result)) {
3991         throw native.getErrorObject(result);
3992     }
3993 };
3994
3995 BluetoothAdapter.prototype.createBonding = function() {
3996     privUtils_.log('Entered BluetoothAdapter.createBonding()');
3997     var args = AV.validateArgs(arguments, [
3998         {
3999             name: 'address',
4000             type: AV.Types.STRING
4001         },
4002         {
4003             name: 'successCallback',
4004             type: AV.Types.FUNCTION,
4005             optional: false,
4006             nullable: false
4007         },
4008         {
4009             name: 'errorCallback',
4010             type: AV.Types.FUNCTION,
4011             optional: true,
4012             nullable: true
4013         }
4014     ]);
4015
4016     var callArgs = {
4017         address: args.address
4018     };
4019
4020     var callback = function(result) {
4021         if (native.isFailure(result)) {
4022             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
4023         } else {
4024             args.successCallback(new BluetoothDevice(native.getResultObject(result)));
4025         }
4026     };
4027
4028     var result = native.call('BluetoothAdapterCreateBonding', callArgs, callback);
4029     if (native.isFailure(result)) {
4030         throw native.getErrorObject(result);
4031     }
4032 };
4033
4034 BluetoothAdapter.prototype.destroyBonding = function() {
4035     privUtils_.log('Entered BluetoothAdapter.destroyBonding()');
4036     var args = AV.validateArgs(arguments, [
4037         {
4038             name: 'address',
4039             type: AV.Types.STRING
4040         },
4041         {
4042             name: 'successCallback',
4043             type: AV.Types.FUNCTION,
4044             optional: true,
4045             nullable: true
4046         },
4047         {
4048             name: 'errorCallback',
4049             type: AV.Types.FUNCTION,
4050             optional: true,
4051             nullable: true
4052         }
4053     ]);
4054
4055     var callArgs = {
4056         address: args.address
4057     };
4058
4059     var callback = function(result) {
4060         if (native.isFailure(result)) {
4061             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
4062         } else {
4063             native.callIfPossible(args.successCallback);
4064         }
4065     };
4066
4067     var result = native.call('BluetoothAdapterDestroyBonding', callArgs, callback);
4068     if (native.isFailure(result)) {
4069         throw native.getErrorObject(result);
4070     }
4071 };
4072
4073 BluetoothAdapter.prototype.registerRFCOMMServiceByUUID = function() {
4074     privUtils_.log('Entered BluetoothAdapter.registerRFCOMMServiceByUUID()');
4075     var args = AV.validateArgs(arguments, [
4076         {
4077             name: 'uuid',
4078             type: AV.Types.STRING
4079         },
4080         {
4081             name: 'name',
4082             type: AV.Types.STRING
4083         },
4084         {
4085             name: 'successCallback',
4086             type: AV.Types.FUNCTION
4087         },
4088         {
4089             name: 'errorCallback',
4090             type: AV.Types.FUNCTION,
4091             optional: true,
4092             nullable: true
4093         }
4094     ]);
4095
4096     var callArgs = {
4097         uuid: args.uuid,
4098         name: args.name
4099     };
4100
4101     var callback = function(result) {
4102         if (native.isFailure(result)) {
4103             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
4104         } else {
4105             // if registration was finished with success create BluetoothServiceHandler
4106             // with parameters passed to this function (uuid and name).
4107             args.successCallback(new BluetoothServiceHandler(callArgs));
4108         }
4109     };
4110
4111     var result = native.call(
4112         'BluetoothAdapterRegisterRFCOMMServiceByUUID',
4113         callArgs,
4114         callback
4115     );
4116     if (native.isFailure(result)) {
4117         throw native.getErrorObject(result);
4118     }
4119 };
4120
4121 BluetoothAdapter.prototype.getBluetoothProfileHandler = function() {
4122     privUtils_.log('Entered BluetoothAdapter.getBluetoothProfileHandler()');
4123     privUtils_.printDeprecationWarningFor('getBluetoothProfileHandler');
4124     var args = AV.validateArgs(arguments, [
4125         {
4126             name: 'profileType',
4127             type: AV.Types.ENUM,
4128             values: T.getValues(_BluetoothProfileType)
4129         }
4130     ]);
4131
4132     var callArgs = { profileType: args.profileType };
4133
4134     var result = native.callSync('BluetoothAdapterGetBluetoothProfileHandler', callArgs);
4135
4136     if (native.isFailure(result)) {
4137         throw native.getErrorObject(result);
4138     } else {
4139         switch (args.profileType) {
4140         case _BluetoothProfileType.HEALTH:
4141             return new BluetoothHealthProfileHandler(callArgs);
4142
4143         default:
4144             throw new WebAPIException(
4145                 'NotSupportedError',
4146                 'Profile ' + args.profileType + ' is not supported.'
4147             );
4148         }
4149     }
4150 };
4151
4152 // class BluetoothGATTServer ////////////////////////
4153 var _BluetoothGATTServerServices = [];
4154 var _isBluetoothGATTServerRunning = false;
4155
4156 function _BluetoothGattServerIsRunningChangeListener(result) {
4157     _isBluetoothGATTServerRunning = result.state;
4158 }
4159
4160 function _BluetoothGattServerBluetoothAdapterStateChangeListener(result) {
4161     if (_isBluetoothGATTServerRunning && false === result.state) {
4162         _BluetoothGATTServerServicesRegisteredInNativeLayer = {};
4163     }
4164 }
4165
4166 /*
4167  * This set is used in BluetoothGATTServer::start() to check which services
4168  * from BluetoothGATTServer::services have already been registered in native
4169  * layer and which have to be registered.
4170  */
4171 var _BluetoothGATTServerServicesRegisteredInNativeLayer = {};
4172
4173 var BluetoothGATTServer = function() {
4174     Object.defineProperties(this, {
4175         services: {
4176             enumerable: true,
4177             get: function() {
4178                 return _BluetoothGATTServerServices;
4179             },
4180             set: function() {}
4181         },
4182         isRunning: {
4183             enumerable: true,
4184             get: function() {
4185                 return _isBluetoothGATTServerRunning;
4186             },
4187             set: function() {}
4188         }
4189     });
4190
4191     // Register listener for managing GATTServer start / stop
4192     native.addListener(
4193         'BluetoothGattServerIsRunningChangeListener',
4194         _BluetoothGattServerIsRunningChangeListener
4195     );
4196
4197     // Register listener for managing BluetoothAdapter power off
4198     native.addListener(
4199         'BluetoothGattServerBluetoothAdapterStateChangeListener',
4200         _BluetoothGattServerBluetoothAdapterStateChangeListener
4201     );
4202 };
4203
4204 var BluetoothGATTServer_valid_registerService_errors = [
4205     'InvalidStateError',
4206     'NotSupportedError',
4207     'InvalidValuesError',
4208     'AbortError'
4209 ];
4210 var BluetoothGATTServer_valid_registerService_exceptions = [
4211     'InvalidStateError',
4212     'TypeMismatchError',
4213     'SecurityError'
4214 ];
4215
4216 BluetoothGATTServer.prototype.registerService = function() {
4217     var args = AV.validateArgs(arguments, [
4218         {
4219             name: 'service',
4220             type: AV.Types.DICTIONARY
4221         },
4222         {
4223             name: 'successCallback',
4224             type: AV.Types.FUNCTION,
4225             optional: true,
4226             nullable: true
4227         },
4228         {
4229             name: 'errorCallback',
4230             type: AV.Types.FUNCTION,
4231             optional: true,
4232             nullable: true
4233         }
4234     ]);
4235
4236     var service = new BluetoothGATTServerService(args.service);
4237
4238     // Callback when registering characteristics and descriptors callbacks is successful
4239     var successCallback = function() {
4240         _BluetoothGATTServerServicesRegisteredInNativeLayer[service._id] = true;
4241         _BluetoothGATTServerServices.push(service);
4242         native.callIfPossible(args.successCallback);
4243     };
4244
4245     // Callback when registering characteristics and descriptors callbacks fails,
4246     // unregister service
4247     var errorCallback = function() {
4248         var unregisterServiceCallback = function() {
4249             native.callIfPossible(args.errorCallback, AbortError);
4250         };
4251
4252         var callArgs = {
4253             _id: service._id,
4254             idsToRemoveFromNativeLayer: _getIncludedServicesAndItsComponentsIdsRecursively(
4255                 service
4256             )
4257         };
4258
4259         native.call(
4260             'BluetoothGATTServerUnregisterService',
4261             callArgs,
4262             unregisterServiceCallback
4263         );
4264     };
4265
4266     var callback = function(result) {
4267         if (native.isFailure(result)) {
4268             native.callIfPossible(
4269                 args.errorCallback,
4270                 native.getErrorObjectAndValidate(
4271                     result,
4272                     BluetoothGATTServer_valid_registerService_errors,
4273                     AbortError
4274                 )
4275             );
4276         } else {
4277             _registerReadWriteValueRequestCallbacksInGATTService(
4278                 service,
4279                 successCallback,
4280                 errorCallback
4281             );
4282         }
4283     };
4284
4285     var result = native.call('BluetoothGATTServerRegisterService', service, callback);
4286     if (native.isFailure(result)) {
4287         throw native.getErrorObjectAndValidate(
4288             result,
4289             BluetoothGATTServer_valid_registerService_exceptions,
4290             AbortError
4291         );
4292     }
4293 };
4294
4295 var BluetoothGATTServer_valid_unregisterAllServices_errors = [
4296     'InvalidStateError',
4297     'AbortError'
4298 ];
4299
4300 var BluetoothGATTServer_valid_unregisterAllServices_exceptions = [
4301     'TypeMismatchError',
4302     'SecurityError'
4303 ];
4304
4305 BluetoothGATTServer.prototype.unregisterAllServices = function() {
4306     privUtils_.log('Entered BluetoothGATTServer.unregisterAllServices()');
4307     var args = AV.validateArgs(arguments, [
4308         {
4309             name: 'successCallback',
4310             type: AV.Types.FUNCTION,
4311             optional: true,
4312             nullable: true
4313         },
4314         {
4315             name: 'errorCallback',
4316             type: AV.Types.FUNCTION,
4317             optional: true,
4318             nullable: true
4319         }
4320     ]);
4321
4322     var servicesToUnregister = [];
4323     for (var i = 0; i < _BluetoothGATTServerServices.length; ++i) {
4324         if (!T.isNullOrUndefined(_BluetoothGATTServerServices[i]._id)) {
4325             servicesToUnregister.push(_BluetoothGATTServerServices[i]);
4326         }
4327     }
4328
4329     if (servicesToUnregister.length) {
4330         privUtils_.log(
4331             'Number of services to unregister: ' + servicesToUnregister.length
4332         );
4333         var unregisterServiceCallbacksAggregator = new ResultCallbacksAggregator(
4334             servicesToUnregister.length,
4335             function onAllSucceeded() {
4336                 _BluetoothGATTServerServices = [];
4337                 _BluetoothGATTServerServicesRegisteredInNativeLayer = [];
4338                 native.callIfPossible(args.successCallback);
4339             },
4340             function onFailure(error) {
4341                 native.callIfPossible(
4342                     args.errorCallback,
4343                     native.getErrorObjectAndValidate(
4344                         error,
4345                         BluetoothGATTServer_valid_unregisterAllServices_errors,
4346                         AbortError
4347                     )
4348                 );
4349             }
4350         );
4351
4352         for (var i = 0; i < servicesToUnregister.length; ++i) {
4353             var serviceIndex = _BluetoothGATTServerServices.findIndex(function(service) {
4354                 return service._id === servicesToUnregister[i]._id;
4355             });
4356
4357             var serviceId = servicesToUnregister[i]._id;
4358
4359             var unregisterServiceCallback = function(result) {
4360                 if (native.isFailure(result)) {
4361                     unregisterServiceCallbacksAggregator.errorCallback(
4362                         native.getErrorObjectAndValidate(
4363                             result,
4364                             BluetoothGATTServer_valid_unregisterAllServices_errors,
4365                             AbortError
4366                         )
4367                     );
4368                 } else {
4369                     delete _BluetoothGATTServerServicesRegisteredInNativeLayer[serviceId];
4370                     _BluetoothGATTServerServices.splice(serviceIndex, 1);
4371                     unregisterServiceCallbacksAggregator.successCallback();
4372                 }
4373             };
4374
4375             var callArgs = {
4376                 _id: servicesToUnregister[i]._id,
4377                 idsToRemoveFromNativeLayer: _getIncludedServicesAndItsComponentsIdsRecursively(
4378                     servicesToUnregister[i]
4379                 )
4380             };
4381
4382             var result = native.call(
4383                 'BluetoothGATTServerUnregisterService',
4384                 callArgs,
4385                 unregisterServiceCallback
4386             );
4387
4388             if (native.isFailure(result)) {
4389                 throw native.getErrorObjectAndValidate(
4390                     result,
4391                     BluetoothGATTServer_valid_unregisterAllServices_exceptions,
4392                     AbortError
4393                 );
4394             }
4395         }
4396     } else {
4397         privUtils_.log(
4398             'Nothing registered in native layer, calling BluetoothGATTServer.stop()'
4399         );
4400
4401         var callback = function(result) {
4402             if (native.isFailure(result)) {
4403                 native.callIfPossible(
4404                     args.errorCallback,
4405                     native.getErrorObjectAndValidate(
4406                         result,
4407                         BluetoothGATTServer_valid_unregisterAllServices_errors,
4408                         AbortError
4409                     )
4410                 );
4411             } else {
4412                 _BluetoothGATTServerServices = [];
4413                 _BluetoothGATTServerServicesRegisteredInNativeLayer = {};
4414                 native.callIfPossible(args.successCallback);
4415             }
4416         };
4417
4418         var result = native.call('BluetoothGATTServerStop', {}, callback);
4419         if (native.isFailure(result)) {
4420             throw native.getErrorObjectAndValidate(
4421                 result,
4422                 BluetoothGATTServer_valid_unregisterAllServices_exceptions,
4423                 AbortError
4424             );
4425         }
4426     }
4427 };
4428
4429 /*
4430  * Objects of this class are used to wait for multiple callbacks results.
4431  *
4432  * Its successCallback and errorCallback members should be passed as
4433  * the success and error callbacks, respectively, to the functions we wait for.
4434  * When the functions we wait for are called callbacksNum times, either
4435  * onAllSucceeded or onFailure is called, depending on whether only
4436  * successCallback was called or not.
4437  *
4438  * For the usage example, take a look at BluetoothGATTServer.prototype.start,
4439  * where it's used to wait for registration of multiple services.
4440  */
4441 var ResultCallbacksAggregator = function(callbacksNum, onAllSucceeded, onFailure) {
4442     var _callbacksNum = callbacksNum;
4443     var _allSucceeded = true;
4444     var _error;
4445
4446     this.successCallback = function() {
4447         _callbacksNum--;
4448
4449         if (!_callbacksNum) {
4450             if (_allSucceeded) {
4451                 onAllSucceeded();
4452             } else {
4453                 onFailure(_error);
4454             }
4455         }
4456     };
4457
4458     this.errorCallback = function(error) {
4459         _callbacksNum--;
4460         _allSucceeded = false;
4461         _error = error;
4462
4463         if (!_callbacksNum) {
4464             onFailure(_error);
4465         }
4466     };
4467 };
4468
4469 function _getServicesUnregisteredInNativeLayer() {
4470     var servicesUnregisteredInNativeLayer = [];
4471     for (var i = 0; i < _BluetoothGATTServerServices.length; ++i) {
4472         if (
4473             !_BluetoothGATTServerServicesRegisteredInNativeLayer[
4474                 _BluetoothGATTServerServices[i]._id
4475             ]
4476         ) {
4477             servicesUnregisteredInNativeLayer.push(_BluetoothGATTServerServices[i]);
4478         }
4479     }
4480     return servicesUnregisteredInNativeLayer;
4481 }
4482
4483 function _getIdsToReregisterReadWriteCallbacks(servicesUnregisteredInNativeLayer) {
4484     var idsToRegisterWriteValueCallbacks = [];
4485     var idsToRegisterReadValueCallbacks = [];
4486
4487     for (var i = 0; i < servicesUnregisteredInNativeLayer.length; ++i) {
4488         var serviceComponentIds = _getIncludedServicesAndItsComponentsIdsRecursively(
4489             servicesUnregisteredInNativeLayer[i]
4490         );
4491
4492         for (var j = 0; j < serviceComponentIds.length; ++j) {
4493             var _id = serviceComponentIds[j];
4494             var callbackName = 'ReadValueCallback' + _id;
4495
4496             /*
4497              * Only some of the serviceComponentIds are characteristics and descriptors
4498              * and within them, only some have read/write callbacks registered.
4499              * We have to check if the callbacks were registered for each id.
4500              */
4501             if (callbackName in _BluetoothGATTServerReadWriteValueRequestCallbacks) {
4502                 idsToRegisterReadValueCallbacks.push(_id);
4503             }
4504
4505             callbackName = 'WriteValueCallback' + _id;
4506             if (callbackName in _BluetoothGATTServerReadWriteValueRequestCallbacks) {
4507                 idsToRegisterWriteValueCallbacks.push(_id);
4508             }
4509         }
4510     }
4511
4512     return [idsToRegisterReadValueCallbacks, idsToRegisterWriteValueCallbacks];
4513 }
4514
4515 var BluetoothGATTServer_valid_start_errors = [
4516     'InvalidStateError',
4517     'NotSupportedError',
4518     'AbortError'
4519 ];
4520 var BluetoothGATTServer_valid_start_exceptions = [
4521     'InvalidStateError',
4522     'TypeMismatchError',
4523     'SecurityError'
4524 ];
4525
4526 function _reregisterCallback(_id, nativeFunction, successCallback, errorCallback) {
4527     /*
4528      * This function should only be used to reregister read/write value request
4529      * callbacks in native layer for characteristics/descriptors, that have
4530      * had such callbacks registered before the last server's stop().
4531      * As JS listeners corresponding to these callbacks already exist in JS,
4532      * only C++ callbacks have to be reregistered.
4533      */
4534     var callback = function(result) {
4535         if (native.isFailure(result)) {
4536             errorCallback(native.getErrorObject(result));
4537         } else {
4538             successCallback();
4539         }
4540     };
4541
4542     var result = native.call(nativeFunction, { _id: _id }, callback);
4543
4544     if (native.isFailure(result)) {
4545         throw native.getErrorObjectAndValidate(
4546             result,
4547             BluetoothGATTServer_valid_start_exceptions,
4548             AbortError
4549         );
4550     }
4551 }
4552
4553 BluetoothGATTServer.prototype.start = function() {
4554     privUtils_.log('Entered BluetoothGATTServer.start()');
4555     var args = AV.validateArgs(arguments, [
4556         {
4557             name: 'successCallback',
4558             type: AV.Types.FUNCTION,
4559             optional: true,
4560             nullable: true
4561         },
4562         {
4563             name: 'errorCallback',
4564             type: AV.Types.FUNCTION,
4565             optional: true,
4566             nullable: true
4567         }
4568     ]);
4569
4570     var servicesUnregisteredInNativeLayer = _getServicesUnregisteredInNativeLayer();
4571     /*
4572      * Characteristics and descriptors that were registered in the server before the last stop() call
4573      * could have had read/write value request callbacks registered.
4574      * These callbacks have to be reregistered (only) in native layer.
4575      * These arrays contain _ids of characteristics and descriptors, that will have their callbacks
4576      * reregistered.
4577      */
4578     var idsToReregisterReadWriteValueCallbacks = _getIdsToReregisterReadWriteCallbacks(
4579         servicesUnregisteredInNativeLayer
4580     );
4581     var idsToReregisterReadValueCallbacks = idsToReregisterReadWriteValueCallbacks[0];
4582     var idsToReregisterWriteValueCallbacks = idsToReregisterReadWriteValueCallbacks[1];
4583
4584     var startServerCallback = function(result) {
4585         if (native.isFailure(result)) {
4586             native.callIfPossible(
4587                 args.errorCallback,
4588                 native.getErrorObjectAndValidate(
4589                     result,
4590                     BluetoothGATTServer_valid_start_errors,
4591                     AbortError
4592                 )
4593             );
4594         } else {
4595             native.callIfPossible(args.successCallback);
4596         }
4597     };
4598
4599     if (servicesUnregisteredInNativeLayer.length) {
4600         var numberOfCallbacksToWaitFor =
4601             servicesUnregisteredInNativeLayer.length +
4602             idsToReregisterReadValueCallbacks.length +
4603             idsToReregisterWriteValueCallbacks.length;
4604         var registerServiceCallbacksAggregator = new ResultCallbacksAggregator(
4605             numberOfCallbacksToWaitFor,
4606             function onAllSucceeded() {
4607                 var result = native.call(
4608                     'BluetoothGATTServerStart',
4609                     {},
4610                     startServerCallback
4611                 );
4612
4613                 if (native.isFailure(result)) {
4614                     throw native.getErrorObjectAndValidate(
4615                         result,
4616                         BluetoothGATTServer_valid_start_exceptions,
4617                         AbortError
4618                     );
4619                 }
4620             },
4621             function onFailure(error) {
4622                 native.callIfPossible(
4623                     args.errorCallback,
4624                     native.getErrorObjectAndValidate(
4625                         error,
4626                         BluetoothGATTServer_valid_start_errors,
4627                         AbortError
4628                     )
4629                 );
4630             }
4631         );
4632
4633         var registerServicesCallback = function(result) {
4634             if (native.isFailure(result)) {
4635                 registerServiceCallbacksAggregator.errorCallback(result);
4636             } else {
4637                 registerServiceCallbacksAggregator.successCallback();
4638             }
4639         };
4640
4641         for (var i = 0; i < servicesUnregisteredInNativeLayer.length; ++i) {
4642             var result = native.call(
4643                 'BluetoothGATTServerRegisterService',
4644                 servicesUnregisteredInNativeLayer[i],
4645                 registerServicesCallback
4646             );
4647             if (native.isFailure(result)) {
4648                 throw native.getErrorObjectAndValidate(
4649                     result,
4650                     BluetoothGATTServer_valid_registerService_exceptions,
4651                     AbortError
4652                 );
4653             }
4654         }
4655
4656         for (var i = 0; i < idsToReregisterReadValueCallbacks.length; ++i) {
4657             _reregisterCallback(
4658                 idsToReregisterReadValueCallbacks[i],
4659                 'BluetoothGATTServerSetReadValueRequestCallback',
4660                 registerServiceCallbacksAggregator.successCallback,
4661                 registerServiceCallbacksAggregator.errorCallback
4662             );
4663         }
4664
4665         for (var i = 0; i < idsToReregisterWriteValueCallbacks.length; ++i) {
4666             _reregisterCallback(
4667                 idsToReregisterWriteValueCallbacks[i],
4668                 'BluetoothGATTServerSetWriteValueRequestCallback',
4669                 registerServiceCallbacksAggregator.successCallback,
4670                 registerServiceCallbacksAggregator.errorCallback
4671             );
4672         }
4673     } else {
4674         var result = native.call('BluetoothGATTServerStart', {}, startServerCallback);
4675
4676         if (native.isFailure(result)) {
4677             throw native.getErrorObjectAndValidate(
4678                 result,
4679                 BluetoothGATTServer_valid_start_exceptions,
4680                 AbortError
4681             );
4682         }
4683     }
4684 };
4685
4686 var BluetoothGATTServer_valid_stop_errors = [
4687     'InvalidStateError',
4688     'NotSupportedError',
4689     'AbortError'
4690 ];
4691 var BluetoothGATTServer_valid_stop_exceptions = [
4692     'InvalidStateError',
4693     'TypeMismatchError',
4694     'SecurityError'
4695 ];
4696
4697 BluetoothGATTServer.prototype.stop = function() {
4698     privUtils_.log('Entered BluetoothGATTServer.stop()');
4699     var args = AV.validateArgs(arguments, [
4700         {
4701             name: 'successCallback',
4702             type: AV.Types.FUNCTION,
4703             optional: true,
4704             nullable: true
4705         },
4706         {
4707             name: 'errorCallback',
4708             type: AV.Types.FUNCTION,
4709             optional: true,
4710             nullable: true
4711         }
4712     ]);
4713
4714     var callback = function(result) {
4715         if (native.isFailure(result)) {
4716             native.callIfPossible(
4717                 args.errorCallback,
4718                 native.getErrorObjectAndValidate(
4719                     result,
4720                     BluetoothGATTServer_valid_stop_errors,
4721                     AbortError
4722                 )
4723             );
4724         } else {
4725             _BluetoothGATTServerServicesRegisteredInNativeLayer = {};
4726             native.callIfPossible(args.successCallback);
4727         }
4728     };
4729
4730     var result = native.call('BluetoothGATTServerStop', {}, callback);
4731     if (native.isFailure(result)) {
4732         throw native.getErrorObjectAndValidate(
4733             result,
4734             BluetoothGATTServer_valid_stop_exceptions,
4735             AbortError
4736         );
4737     }
4738 };
4739
4740 var BluetoothGATTServer_valid_getConnectionMtu_errors = [
4741     'InvalidStateError',
4742     'NotSupportedError',
4743     'AbortError'
4744 ];
4745 var BluetoothGATTServer_valid_getConnectionMtu_exceptions = [
4746     'TypeMismatchError',
4747     'SecurityError'
4748 ];
4749
4750 BluetoothGATTServer.prototype.getConnectionMtu = function() {
4751     privUtils_.log('Entered BluetoothGATTServer.getConnectionMtu()');
4752     var args = AV.validateArgs(arguments, [
4753         {
4754             name: 'clientAddress',
4755             type: AV.Types.STRING
4756         },
4757         {
4758             name: 'successCallback',
4759             type: AV.Types.FUNCTION
4760         },
4761         {
4762             name: 'errorCallback',
4763             type: AV.Types.FUNCTION,
4764             optional: true
4765         }
4766     ]);
4767
4768     var callback = function(result) {
4769         if (native.isFailure(result)) {
4770             native.callIfPossible(
4771                 args.errorCallback,
4772                 native.getErrorObjectAndValidate(
4773                     result,
4774                     BluetoothGATTServer_valid_getConnectionMtu_errors,
4775                     AbortError
4776                 )
4777             );
4778         } else {
4779             args.successCallback(native.getResultObject(result));
4780         }
4781     };
4782
4783     var result = native.call(
4784         'BluetoothGATTServerGetConnectionMtu',
4785         { clientAddress: args.clientAddress },
4786         callback
4787     );
4788     if (native.isFailure(result)) {
4789         throw native.getErrorObjectAndValidate(
4790             result,
4791             BluetoothGATTServer_valid_getConnectionMtu_exceptions,
4792             AbortError
4793         );
4794     }
4795 };
4796
4797 var GATTServer = new BluetoothGATTServer();
4798
4799 // class BluetoothManager ///////////////////////////
4800 var BluetoothManager = function() {
4801     Object.defineProperties(this, {
4802         deviceMajor: {
4803             value: new BluetoothClassDeviceMajor(),
4804             writable: false,
4805             enumerable: true
4806         },
4807         deviceMinor: {
4808             value: new BluetoothClassDeviceMinor(),
4809             writable: false,
4810             enumerable: true
4811         },
4812         deviceService: {
4813             value: new BluetoothClassDeviceService(),
4814             writable: false,
4815             enumerable: true
4816         },
4817         BASE_UUID: {
4818             value: '00000000-0000-1000-8000-00805F9B34FB',
4819             writable: false,
4820             enumerable: true
4821         }
4822     });
4823 };
4824
4825 var BluetoothManager_getDefaultAdapter = function() {
4826     privUtils_.checkPrivilegeAccess4Ver(
4827         '2.4',
4828         Privilege.BLUETOOTH,
4829         Privilege.BLUETOOTH_GAP
4830     );
4831
4832     return new BluetoothAdapter();
4833 };
4834
4835 BluetoothManager.prototype.getDefaultAdapter = function() {
4836     privUtils_.log('Entered BluetoothManager.getDefaultAdapter()');
4837     return BluetoothManager_getDefaultAdapter();
4838 };
4839
4840 var BluetoothManager_getLEAdapter = function() {
4841     privUtils_.checkPrivilegeAccess4Ver(
4842         '2.4',
4843         Privilege.BLUETOOTH,
4844         Privilege.BLUETOOTH_ADMIN
4845     );
4846
4847     return new BluetoothLEAdapter();
4848 };
4849
4850 BluetoothManager.prototype.getLEAdapter = function() {
4851     privUtils_.log('Entered BluetoothManager.getLEAdapter()');
4852     return BluetoothManager_getLEAdapter();
4853 };
4854
4855 var BluetoothManager_checkAndNormalizeHexString = function(hexString) {
4856     hexString = hexString.toLowerCase();
4857     if (hexString.startsWith('0x')) {
4858         hexString = hexString.substring(2);
4859     }
4860     if (!/^[0-9a-f]+$/.test(hexString)) {
4861         throw new WebAPIException(
4862             WebAPIException.TYPE_MISMATCH_ERR,
4863             'Given string is not hexadecimal value'
4864         );
4865     }
4866     if ('0' === hexString) {
4867         return '00';
4868     }
4869     if (1 === hexString.length % 2) {
4870         // to save consistency with BluetoothLEManufacturerData, last character is omitted
4871         hexString = hexString.replace(/.$/, '');
4872     }
4873     return hexString;
4874 };
4875
4876 var BluetoothManager_HexStringToUint8Array = function(hexString) {
4877     hexString = BluetoothManager_checkAndNormalizeHexString(hexString);
4878     if (0 === hexString.length) {
4879         return new Uint8Array([]);
4880     }
4881     var data = hexString.match(/[0-9a-f]{2}/g).map(function(byte) {
4882         return parseInt(byte, 16);
4883     });
4884     return new Uint8Array(data);
4885 };
4886
4887 var BluetoothManager_byteArrayToHexString = function(bytes) {
4888     if (0 == bytes.length) {
4889         return '';
4890     }
4891     return (
4892         '0x' +
4893         Array.prototype.map
4894             .call(bytes, function(byte) {
4895                 return ('0' + (byte & 0xff).toString(16)).slice(-2);
4896             })
4897             .join('')
4898     );
4899 };
4900
4901 var BluetoothManager_toByteArray = function(data) {
4902     if (data && String === data.constructor) {
4903         return numberArrayToByteArray(BluetoothManager_HexStringToUint8Array(data));
4904     } else {
4905         try {
4906             data = numberArrayToByteArray(data);
4907         } catch (err) {
4908             throw new WebAPIException(
4909                 WebAPIException.TYPE_MISMATCH_ERR,
4910                 'argument is not a valid Bytes type'
4911             );
4912         }
4913     }
4914     return data;
4915 };
4916
4917 BluetoothManager.prototype.toByteArray = function(data) {
4918     privUtils_.log('Entered BluetoothManager.toByteArray()');
4919     return BluetoothManager_toByteArray(data);
4920 };
4921
4922 var BluetoothManager_toDOMString = function(data) {
4923     if (data && String === data.constructor) {
4924         data = BluetoothManager_checkAndNormalizeHexString(data);
4925         if (0 !== data.length) {
4926             data = '0x' + data;
4927         }
4928         return data;
4929     }
4930     try {
4931         data = numberArrayToByteArray(data);
4932     } catch (err) {
4933         throw new WebAPIException(
4934             WebAPIException.TYPE_MISMATCH_ERR,
4935             'argument is not a valid Bytes type'
4936         );
4937     }
4938     return BluetoothManager_byteArrayToHexString(data);
4939 };
4940
4941 BluetoothManager.prototype.toDOMString = function(data) {
4942     privUtils_.log('Entered BluetoothManager.toDOMString()');
4943     return BluetoothManager_toDOMString(data);
4944 };
4945
4946 var BluetoothManager_toUint8Array = function(data) {
4947     if (data && String === data.constructor) {
4948         return BluetoothManager_HexStringToUint8Array(data);
4949     } else {
4950         try {
4951             data = new Uint8Array(numberArrayToByteArray(data));
4952         } catch (err) {
4953             throw new WebAPIException(
4954                 WebAPIException.TYPE_MISMATCH_ERR,
4955                 'argument is not a valid Bytes type'
4956             );
4957         }
4958     }
4959     return data;
4960 };
4961
4962 BluetoothManager.prototype.toUint8Array = function(data) {
4963     privUtils_.log('Entered BluetoothManager.toUint8Array()');
4964     return BluetoothManager_toUint8Array(data);
4965 };
4966
4967 var BluetoothManager_UUIDIsValid128Bit = function(uuid) {
4968     var re128BitFormat = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
4969     if (re128BitFormat.test(uuid)) {
4970         return true;
4971     }
4972     return false;
4973 };
4974
4975 var BluetoothManager_UUIDIsValid32Bit = function(uuid) {
4976     var re32BitFormat = /^[0-9a-f]{8}$/;
4977     if (re32BitFormat.test(uuid)) {
4978         return true;
4979     }
4980     return false;
4981 };
4982
4983 var BluetoothManager_UUIDIsValid16Bit = function(uuid) {
4984     var re16BitFormat = /^[0-9a-f]{4}$/;
4985     if (re16BitFormat.test(uuid)) {
4986         return true;
4987     }
4988     return false;
4989 };
4990
4991 var BluetoothManager_UUIDIsConvertibleTo16Bit = function(uuid) {
4992     var re128BitFormat = /^0000[0-9a-f]{4}-0000-1000-8000-00805f9b34fb$/;
4993     if (re128BitFormat.test(uuid)) {
4994         return true;
4995     }
4996     var re32BitFormat = /^0000[0-9a-f]{4}$/;
4997     if (re32BitFormat.test(uuid)) {
4998         return true;
4999     }
5000     return false;
5001 };
5002
5003 var BluetoothManager_UUIDIsConvertibleTo32Bit = function(uuid) {
5004     var re = /^[0-9a-f]{8}-0000-1000-8000-00805f9b34fb$/;
5005     if (re.test(uuid)) {
5006         return true;
5007     }
5008     return false;
5009 };
5010
5011 var BluetoothManager_UUIDTo128bit = function(uuid) {
5012     uuid = Converter.toString(uuid).toLowerCase();
5013     if (BluetoothManager_UUIDIsValid128Bit(uuid)) {
5014         return uuid;
5015     }
5016     var baseUuidLast96Bits = '-0000-1000-8000-00805f9b34fb';
5017     if (BluetoothManager_UUIDIsValid16Bit(uuid)) {
5018         return '0000' + uuid + baseUuidLast96Bits;
5019     }
5020     if (BluetoothManager_UUIDIsValid32Bit(uuid)) {
5021         return uuid + baseUuidLast96Bits;
5022     }
5023     throw new WebAPIException(
5024         WebAPIException.INVALID_VALUES_ERR,
5025         'Given parameter is not a supported uuid format: ' + uuid
5026     );
5027 };
5028
5029 var BluetoothManager_UUIDToShortestPossible = function(uuid) {
5030     uuid = uuid.toLowerCase();
5031     if (BluetoothManager_UUIDIsValid16Bit(uuid)) {
5032         return uuid;
5033     }
5034     if (BluetoothManager_UUIDIsValid32Bit(uuid)) {
5035         if (BluetoothManager_UUIDIsConvertibleTo16Bit(uuid)) {
5036             return uuid.substring(4, 8);
5037         }
5038     }
5039     if (!BluetoothManager_UUIDIsValid128Bit(uuid)) {
5040         throw new WebAPIException(
5041             WebAPIException.INVALID_VALUES_ERR,
5042             'Given parameter is not a supported uuid format: ' + uuid
5043         );
5044     }
5045     if (BluetoothManager_UUIDIsConvertibleTo16Bit(uuid)) {
5046         return uuid.substring(4, 8);
5047     }
5048     if (BluetoothManager_UUIDIsConvertibleTo32Bit(uuid)) {
5049         return uuid.substring(0, 8);
5050     }
5051     return uuid;
5052 };
5053
5054 var BluetoothManager_UUIDsEqual = function(uuid1, uuid2) {
5055     uuid1 = Converter.toString(uuid1).toLowerCase();
5056     uuid1 = BluetoothManager_UUIDTo128bit(uuid1);
5057     uuid2 = Converter.toString(uuid2).toLowerCase();
5058     uuid2 = BluetoothManager_UUIDTo128bit(uuid2);
5059     return uuid1 === uuid2;
5060 };
5061
5062 BluetoothManager.prototype.uuidTo128bit = function(uuid) {
5063     privUtils_.log('Entered BluetoothManager.uuidTo128bit()');
5064     var args = AV.validateArgs(arguments, [
5065         {
5066             name: 'uuid',
5067             type: AV.Types.STRING
5068         }
5069     ]);
5070     return BluetoothManager_UUIDTo128bit(args.uuid);
5071 };
5072
5073 BluetoothManager.prototype.uuidToShortestPossible = function(uuid) {
5074     privUtils_.log('Entered BluetoothManager.uuidToShortestPossible()');
5075     var args = AV.validateArgs(arguments, [
5076         {
5077             name: 'uuid',
5078             type: AV.Types.STRING
5079         }
5080     ]);
5081     return BluetoothManager_UUIDToShortestPossible(args.uuid);
5082 };
5083
5084 BluetoothManager.prototype.uuidsEqual = function(uuid1, uuid2) {
5085     privUtils_.log('Entered BluetoothManager.uuidsEqual()');
5086     var args = AV.validateArgs(arguments, [
5087         {
5088             name: 'uuid1',
5089             type: AV.Types.STRING
5090         },
5091         {
5092             name: 'uuid2',
5093             type: AV.Types.STRING
5094         }
5095     ]);
5096     return BluetoothManager_UUIDsEqual(args.uuid1, args.uuid2);
5097 };
5098 BluetoothManager.prototype.getGATTServer = function() {
5099     privUtils_.log('Entered BluetoothManager.getGATTServer()');
5100     return BluetoothManager_getGATTServer();
5101 };
5102
5103 var BluetoothManager_getGATTServer = function() {
5104     return GATTServer;
5105 };
5106
5107 // exports //////////////////////////////////////////
5108 exports = new BluetoothManager();